Skip to content

Commit 05a0b8a

Browse files
committed
transaction: avoid quadratic scaling when growing arrays
wally_tx_witness_stack_set(), wally_tx_add_input_at(), and wally_tx_add_output_at() all exhibited quadratic scaling behavior when iteratively appending elements to their respective arrays because they were calling array_realloc() to expand their arrays by one element at a time. Change them to use array_grow(), and change the contract of array_grow() so that it accepts a minimum element count and ensures that the array is at least large enough to accommodate that many elements, enlarging it to the next greater power of two if necessary. See: ElementsProject/lightning#8924 Signed-off-by: Matt Whitlock <c-lightning@mattwhitlock.name>
1 parent 6439e6e commit 05a0b8a

5 files changed

Lines changed: 49 additions & 48 deletions

File tree

src/internal.c

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -619,8 +619,30 @@ int replace_bytes(const unsigned char *bytes, size_t bytes_len,
619619
}
620620

621621

622+
static size_t ceil2(size_t s)
623+
{
624+
--s;
625+
s |= s >> 1;
626+
s |= s >> 2;
627+
s |= s >> 4;
628+
#ifndef SIZE_MAX
629+
# error SIZE_MAX not defined
630+
#endif
631+
#if SIZE_MAX > UINT8_MAX
632+
s |= s >> 8;
633+
#endif
634+
#if SIZE_MAX > UINT16_MAX
635+
s |= s >> 16;
636+
#endif
637+
#if SIZE_MAX > UINT32_MAX
638+
s |= s >> 32;
639+
#endif
640+
++s;
641+
return s + !s;
642+
}
622643

623-
void *array_realloc(const void *src, size_t old_n, size_t new_n, size_t size)
644+
/* Don't use this to build up an array iteratively! Use array_grow. */
645+
static void *array_realloc(const void *src, size_t old_n, size_t new_n, size_t size)
624646
{
625647
unsigned char *p = wally_malloc(new_n * size);
626648
if (!p)
@@ -631,17 +653,17 @@ void *array_realloc(const void *src, size_t old_n, size_t new_n, size_t size)
631653
return p;
632654
}
633655

634-
int array_grow(void **src, size_t num_items, size_t *allocation_len,
656+
int array_grow(void **src, size_t num_items_needed, size_t *allocation_len,
635657
size_t item_size)
636658
{
637-
if (num_items == *allocation_len) {
638-
/* Array is full, allocate more space */
639-
const size_t n = (*allocation_len == 0 ? 1 : *allocation_len) * 2;
659+
if (num_items_needed > *allocation_len) {
660+
/* Array needs grown; allocate more space */
661+
const size_t n = ceil2(num_items_needed);
640662
void *p = array_realloc(*src, *allocation_len, n, item_size);
641663
if (!p)
642664
return WALLY_ENOMEM;
643665
/* Free and replace the old array with the new enlarged copy */
644-
clear_and_free(*src, num_items * item_size);
666+
clear_and_free(*src, *allocation_len * item_size);
645667
*src = p;
646668
*allocation_len = n;
647669
}

src/internal.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ bool clone_data(void **dst, const void *src, size_t len);
101101
bool clone_bytes(unsigned char **dst, const unsigned char *src, size_t len);
102102
int replace_bytes(const unsigned char *bytes, size_t bytes_len,
103103
unsigned char **bytes_out, size_t *bytes_len_out);
104-
void *array_realloc(const void *src, size_t old_n, size_t new_n, size_t size);
105-
106-
int array_grow(void **src, size_t num_items, size_t *allocation_len,
104+
int array_grow(void **src, size_t num_items_needed, size_t *allocation_len,
107105
size_t item_size);
108106

109107
struct ext_key;

src/map.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ int map_add(struct wally_map *map_in,
246246
return ignore_dups ? WALLY_OK : WALLY_EINVAL;
247247
}
248248

249-
ret = array_grow((void *)&map_in->items, map_in->num_items,
249+
ret = array_grow((void *)&map_in->items, map_in->num_items + 1,
250250
&map_in->items_allocation_len, sizeof(struct wally_map_item));
251251
if (ret == WALLY_OK) {
252252
struct wally_map_item *new_item = map_in->items + map_in->num_items;

src/psbt.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,7 +1785,7 @@ int wally_psbt_add_tx_input_at(struct wally_psbt *psbt,
17851785
return ret;
17861786

17871787
if (psbt->num_inputs >= psbt->inputs_allocation_len &&
1788-
(ret = array_grow((void *)&psbt->inputs, psbt->num_inputs,
1788+
(ret = array_grow((void *)&psbt->inputs, psbt->num_inputs + 1,
17891789
&psbt->inputs_allocation_len,
17901790
sizeof(*psbt->inputs))) != WALLY_OK)
17911791
return ret;
@@ -1950,7 +1950,7 @@ int wally_psbt_add_tx_output_at(struct wally_psbt *psbt,
19501950
return ret;
19511951

19521952
if (psbt->num_outputs >= psbt->outputs_allocation_len &&
1953-
(ret = array_grow((void *)&psbt->outputs, psbt->num_outputs,
1953+
(ret = array_grow((void *)&psbt->outputs, psbt->num_outputs + 1,
19541954
&psbt->outputs_allocation_len,
19551955
sizeof(*psbt->outputs))) != WALLY_OK)
19561956
return ret;

src/transaction.c

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ int wally_tx_witness_stack_set(struct wally_tx_witness_stack *stack, size_t inde
272272
const unsigned char *witness, size_t witness_len)
273273
{
274274
unsigned char *new_witness = NULL;
275+
int ret;
275276

276277
if (!is_valid_witness_stack(stack) || (!witness && witness_len))
277278
return WALLY_EINVAL;
@@ -280,18 +281,12 @@ int wally_tx_witness_stack_set(struct wally_tx_witness_stack *stack, size_t inde
280281
return WALLY_ENOMEM;
281282

282283
if (index >= stack->num_items) {
283-
if (index >= stack->items_allocation_len) {
284-
/* Expand the witness array */
285-
struct wally_tx_witness_item *p;
286-
p = array_realloc(stack->items, stack->items_allocation_len,
287-
index + 1, sizeof(*stack->items));
288-
if (!p) {
289-
clear_and_free(new_witness, witness_len);
290-
return WALLY_ENOMEM;
291-
}
292-
clear_and_free(stack->items, stack->num_items * sizeof(*stack->items));
293-
stack->items = p;
294-
stack->items_allocation_len = index + 1;
284+
/* Expand the witness array if needed */
285+
ret = array_grow((void *)&stack->items, index + 1,
286+
&stack->items_allocation_len, sizeof(*stack->items));
287+
if (ret != WALLY_OK) {
288+
clear_and_free(new_witness, witness_len);
289+
return ret;
295290
}
296291
stack->num_items = index + 1;
297292
}
@@ -1198,18 +1193,11 @@ int wally_tx_add_input_at(struct wally_tx *tx, uint32_t index,
11981193
if (!is_valid_tx(tx) || index > tx->num_inputs || !is_valid_tx_input(input))
11991194
return WALLY_EINVAL;
12001195

1201-
if (tx->num_inputs >= tx->inputs_allocation_len) {
1202-
/* Expand the inputs array */
1203-
struct wally_tx_input *p;
1204-
p = array_realloc(tx->inputs, tx->inputs_allocation_len,
1205-
tx->num_inputs + 1, sizeof(*tx->inputs));
1206-
if (!p)
1207-
return WALLY_ENOMEM;
1208-
1209-
clear_and_free(tx->inputs, tx->num_inputs * sizeof(*tx->inputs));
1210-
tx->inputs = p;
1211-
tx->inputs_allocation_len += 1;
1212-
}
1196+
/* Expand the inputs array if needed */
1197+
ret = array_grow((void *)&tx->inputs, tx->num_inputs + 1,
1198+
&tx->inputs_allocation_len, sizeof(*tx->inputs));
1199+
if (ret != WALLY_OK)
1200+
return ret;
12131201

12141202
memmove(tx->inputs + index + 1, tx->inputs + index,
12151203
(tx->num_inputs - index) * sizeof(*input));
@@ -1427,18 +1415,11 @@ int wally_tx_add_output_at(struct wally_tx *tx, uint32_t index,
14271415
} else if (!is_valid_elements_tx_output(output))
14281416
return WALLY_EINVAL;
14291417

1430-
if (tx->num_outputs >= tx->outputs_allocation_len) {
1431-
/* Expand the outputs array */
1432-
struct wally_tx_output *p;
1433-
p = array_realloc(tx->outputs, tx->outputs_allocation_len,
1434-
tx->num_outputs + 1, sizeof(*tx->outputs));
1435-
if (!p)
1436-
return WALLY_ENOMEM;
1437-
1438-
clear_and_free(tx->outputs, tx->num_outputs * sizeof(*tx->outputs));
1439-
tx->outputs = p;
1440-
tx->outputs_allocation_len += 1;
1441-
}
1418+
/* Expand the outputs array if needed */
1419+
ret = array_grow((void *)&tx->outputs, tx->num_outputs + 1,
1420+
&tx->outputs_allocation_len, sizeof(*tx->outputs));
1421+
if (ret != WALLY_OK)
1422+
return ret;
14421423

14431424
memmove(tx->outputs + index + 1, tx->outputs + index,
14441425
(tx->num_outputs - index) * sizeof(*output));

0 commit comments

Comments
 (0)