]> xenbits.xensource.com Git - libvirt.git/commitdiff
memory: make it easier to avoid quadratic scaling of arrays
authorEric Blake <eblake@redhat.com>
Sat, 14 Aug 2010 15:42:51 +0000 (09:42 -0600)
committerEric Blake <eblake@redhat.com>
Thu, 18 Nov 2010 19:17:49 +0000 (12:17 -0700)
* src/util/memory.h (VIR_RESIZE_N): New macro.
* src/util/memory.c (virResizeN): New function.
* src/libvirt_private.syms: Export new helper.
* docs/hacking.html.in: Document it.
* HACKING: Regenerate.

HACKING
docs/hacking.html.in
src/libvirt_private.syms
src/util/memory.c
src/util/memory.h

diff --git a/HACKING b/HACKING
index 4a9516745a1ad5ef01027d64b79f9c185f42487f..35ceb29822f0c4381f9ea4d850b27239dbaef0d5 100644 (file)
--- a/HACKING
+++ b/HACKING
@@ -308,7 +308,7 @@ routines, use the macros from memory.h.
 - To allocate an array of object pointers:
 
   virDomainPtr *domains;
-  int ndomains = 10;
+  size_t ndomains = 10;
 
   if (VIR_ALLOC_N(domains, ndomains) < 0) {
       virReportOOMError();
@@ -317,24 +317,57 @@ routines, use the macros from memory.h.
 
 
 
-- To re-allocate the array of domains to be longer:
+- To re-allocate the array of domains to be 1 element longer (however, note that
+repeatedly expanding an array by 1 scales quadratically, so this is
+recommended only for smaller arrays):
+
+  virDomainPtr domains;
+  size_t ndomains = 0;
 
-  if (VIR_EXPAND_N(domains, ndomains, 10) < 0) {
+  if (VIR_EXPAND_N(domains, ndomains, 1) < 0) {
       virReportOOMError();
       return NULL;
   }
+  domains[ndomains - 1] = domain;
+
+
+
+- To ensure an array has room to hold at least one more element (this approach
+scales better, but requires tracking allocation separately from usage)
+
+  virDomainPtr domains;
+  size_t ndomains = 0;
+  size_t ndomains_max = 0;
+
+  if (VIR_RESIZE_N(domains, ndomains_max, ndomains, 1) < 0) {
+      virReportOOMError();
+      return NULL;
+  }
+  domains[ndomains++] = domain;
 
 
 
 - To trim an array of domains to have one less element:
 
-  VIR_SHRINK_N(domains, ndomains, 1);
+  virDomainPtr domains;
+  size_t ndomains = x;
+  size_t ndomains_max = y;
+
+  VIR_SHRINK_N(domains, ndomains_max, 1);
 
 
 
-- To free the domain:
+- To free an array of domains:
 
-  VIR_FREE(domain);
+  virDomainPtr domains;
+  size_t ndomains = x;
+  size_t ndomains_max = y;
+  size_t i;
+
+  for (i = 0; i < ndomains; i++)
+      VIR_FREE(domains[i]);
+  VIR_FREE(domains);
+  ndomains_max = ndomains = 0;
 
 
 
index 964904e3db3b4ba8585bbbd9d1dd11ca96bcecaa..890692f05803c5e37778face4f03e82fd5e47768 100644 (file)
       <li><p>To allocate an array of object pointers:</p>
 <pre>
   virDomainPtr *domains;
-  int ndomains = 10;
+  size_t ndomains = 10;
 
   if (VIR_ALLOC_N(domains, ndomains) &lt; 0) {
       virReportOOMError();
 </pre>
       </li>
 
-      <li><p>To re-allocate the array of domains to be longer:</p>
+      <li><p>To re-allocate the array of domains to be 1 element
+      longer (however, note that repeatedly expanding an array by 1
+      scales quadratically, so this is recommended only for smaller
+      arrays):</p>
 <pre>
-  if (VIR_EXPAND_N(domains, ndomains, 10) &lt; 0) {
+  virDomainPtr domains;
+  size_t ndomains = 0;
+
+  if (VIR_EXPAND_N(domains, ndomains, 1) &lt; 0) {
       virReportOOMError();
       return NULL;
   }
+  domains[ndomains - 1] = domain;
+</pre></li>
+
+      <li><p>To ensure an array has room to hold at least one more
+      element (this approach scales better, but requires tracking
+      allocation separately from usage)</p>
+
+<pre>
+  virDomainPtr domains;
+  size_t ndomains = 0;
+  size_t ndomains_max = 0;
+
+  if (VIR_RESIZE_N(domains, ndomains_max, ndomains, 1) &lt; 0) {
+      virReportOOMError();
+      return NULL;
+  }
+  domains[ndomains++] = domain;
 </pre>
       </li>
 
       <li><p>To trim an array of domains to have one less element:</p>
 
 <pre>
-  VIR_SHRINK_N(domains, ndomains, 1);
+  virDomainPtr domains;
+  size_t ndomains = x;
+  size_t ndomains_max = y;
+
+  VIR_SHRINK_N(domains, ndomains_max, 1);
 </pre></li>
 
-      <li><p>To free the domain:</p>
+      <li><p>To free an array of domains:</p>
 <pre>
-  VIR_FREE(domain);
+  virDomainPtr domains;
+  size_t ndomains = x;
+  size_t ndomains_max = y;
+  size_t i;
+
+  for (i = 0; i &lt; ndomains; i++)
+      VIR_FREE(domains[i]);
+  VIR_FREE(domains);
+  ndomains_max = ndomains = 0;
 </pre>
-       </li>
+      </li>
     </ul>
 
     <h2><a name="file_handling">File handling</a></h2>
index 796559c597829a3945557ab47a02587f0a9bb988..8bf102839d75407fb9140f07cbb1746b81e9561c 100644 (file)
@@ -506,6 +506,7 @@ virAllocN;
 virExpandN;
 virFree;
 virReallocN;
+virResizeN;
 virShrinkN;
 
 
index 59685b30ef02e5f277a2aeeb47390468d5f46e71..96222db3d3f8a440b155dc298bb9983af8b107a9 100644 (file)
@@ -197,6 +197,41 @@ int virExpandN(void *ptrptr, size_t size, size_t *countptr, size_t add)
     return ret;
 }
 
+/**
+ * virResizeN:
+ * @ptrptr: pointer to pointer for address of allocated memory
+ * @size: number of bytes per element
+ * @allocptr: pointer to number of elements allocated in array
+ * @count: number of elements currently used in array
+ * @add: minimum number of additional elements to support in array
+ *
+ * If 'count' + 'add' is larger than '*allocptr', then resize the
+ * block of memory in 'ptrptr' to be an array of at least 'count' +
+ * 'add' elements, each 'size' bytes in length. Update 'ptrptr' and
+ * 'allocptr' with the details of the newly allocated memory. On
+ * failure, 'ptrptr' and 'allocptr' are not changed. Any newly
+ * allocated memory in 'ptrptr' is zero-filled.
+ *
+ * Returns -1 on failure to allocate, zero on success
+ */
+int virResizeN(void *ptrptr, size_t size, size_t *allocptr, size_t count,
+               size_t add)
+{
+    size_t delta;
+
+    if (count + add < count) {
+        errno = ENOMEM;
+        return -1;
+    }
+    if (count + add <= *allocptr)
+        return 0;
+
+    delta = count + add - *allocptr;
+    if (delta < *allocptr / 2)
+        delta = *allocptr / 2;
+    return virExpandN(ptrptr, size, allocptr, delta);
+}
+
 /**
  * virShrinkN:
  * @ptrptr: pointer to pointer for address of allocated memory
index 98ac2b358209d6ff12b36ce1a7c8d1e9f641f79f..750a6b01817da5d130f3e2c11b19a93e16bc4327 100644 (file)
@@ -54,6 +54,9 @@ int virReallocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK
     ATTRIBUTE_NONNULL(1);
 int virExpandN(void *ptrptr, size_t size, size_t *count, size_t add)
     ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3);
+int virResizeN(void *ptrptr, size_t size, size_t *alloc, size_t count,
+               size_t desired)
+    ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3);
 void virShrinkN(void *ptrptr, size_t size, size_t *count, size_t remove)
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3);
 int virAllocVar(void *ptrptr,
@@ -116,6 +119,29 @@ void virFree(void *ptrptr) ATTRIBUTE_NONNULL(1);
 # define VIR_EXPAND_N(ptr, count, add) \
     virExpandN(&(ptr), sizeof(*(ptr)), &(count), add)
 
+/**
+ * VIR_RESIZE_N:
+ * @ptr: pointer to hold address of allocated memory
+ * @alloc: variable tracking number of elements currently allocated
+ * @count: number of elements currently in use
+ * @add: minimum number of elements to additionally support
+ *
+ * Blindly using VIR_EXPAND_N(array, alloc, 1) in a loop scales
+ * quadratically, because every iteration must copy contents from
+ * all prior iterations.  But amortized linear scaling can be achieved
+ * by tracking allocation size separately from the number of used
+ * elements, and growing geometrically only as needed.
+ *
+ * If 'count' + 'add' is larger than 'alloc', then geometrically reallocate
+ * the array of 'alloc' elements, each sizeof(*ptr) bytes long, and store
+ * the address of allocated memory in 'ptr' and the new size in 'alloc'.
+ * The new elements are filled with zero.
+ *
+ * Returns -1 on failure, 0 on success
+ */
+# define VIR_RESIZE_N(ptr, alloc, count, add) \
+    virResizeN(&(ptr), sizeof(*(ptr)), &(alloc), count, add)
+
 /**
  * VIR_SHRINK_N:
  * @ptr: pointer to hold address of allocated memory