Array#push causes “stack level too deep” error with large arrays
up vote
25
down vote
favorite
I made two arrays, each with 1 million items:
a1 = 1_000_000.times.to_a
a2 = a1.clone
I tried to push a2 into a1:
a1.push *a2
This returns SystemStackError: stack level too deep
.
However, when I try with concat
, I don't get the error:
a1.concat a2
a1.length # => 2_000_000
I also don't get the error with the splat operator:
a3 = [*a1, *a2]
a3.length # => 2_000_000
Why is this the case? I looked at the documentation for Array#push
, and it is written in C. I suspect that it may be doing some recursion under the hood, and that's why it's causing this error for large arrays. Is this correct? Is it not a good idea to use push
for large arrays?
arrays ruby
add a comment |
up vote
25
down vote
favorite
I made two arrays, each with 1 million items:
a1 = 1_000_000.times.to_a
a2 = a1.clone
I tried to push a2 into a1:
a1.push *a2
This returns SystemStackError: stack level too deep
.
However, when I try with concat
, I don't get the error:
a1.concat a2
a1.length # => 2_000_000
I also don't get the error with the splat operator:
a3 = [*a1, *a2]
a3.length # => 2_000_000
Why is this the case? I looked at the documentation for Array#push
, and it is written in C. I suspect that it may be doing some recursion under the hood, and that's why it's causing this error for large arrays. Is this correct? Is it not a good idea to use push
for large arrays?
arrays ruby
1
I don't have an exact answer, but in general I'd say that passing exceedingly large argument lists (where "exceedingly" is defined as upwards of a couple hundred) tends to cause strange behavior, since argument lists are often stored somewhere in memory that makes assumptions about their size. Some languages even define a maximum argument count explicitly; for example, in Common Lisp the variableCALL-ARGUMENTS-LIMIT
tells you the maximum number of arguments.
– Silvio Mayolo
Aug 24 at 2:22
add a comment |
up vote
25
down vote
favorite
up vote
25
down vote
favorite
I made two arrays, each with 1 million items:
a1 = 1_000_000.times.to_a
a2 = a1.clone
I tried to push a2 into a1:
a1.push *a2
This returns SystemStackError: stack level too deep
.
However, when I try with concat
, I don't get the error:
a1.concat a2
a1.length # => 2_000_000
I also don't get the error with the splat operator:
a3 = [*a1, *a2]
a3.length # => 2_000_000
Why is this the case? I looked at the documentation for Array#push
, and it is written in C. I suspect that it may be doing some recursion under the hood, and that's why it's causing this error for large arrays. Is this correct? Is it not a good idea to use push
for large arrays?
arrays ruby
I made two arrays, each with 1 million items:
a1 = 1_000_000.times.to_a
a2 = a1.clone
I tried to push a2 into a1:
a1.push *a2
This returns SystemStackError: stack level too deep
.
However, when I try with concat
, I don't get the error:
a1.concat a2
a1.length # => 2_000_000
I also don't get the error with the splat operator:
a3 = [*a1, *a2]
a3.length # => 2_000_000
Why is this the case? I looked at the documentation for Array#push
, and it is written in C. I suspect that it may be doing some recursion under the hood, and that's why it's causing this error for large arrays. Is this correct? Is it not a good idea to use push
for large arrays?
arrays ruby
arrays ruby
edited Sep 5 at 15:37
shohrukh
1,3011623
1,3011623
asked Aug 23 at 23:17
Kapil Dewade
12813
12813
1
I don't have an exact answer, but in general I'd say that passing exceedingly large argument lists (where "exceedingly" is defined as upwards of a couple hundred) tends to cause strange behavior, since argument lists are often stored somewhere in memory that makes assumptions about their size. Some languages even define a maximum argument count explicitly; for example, in Common Lisp the variableCALL-ARGUMENTS-LIMIT
tells you the maximum number of arguments.
– Silvio Mayolo
Aug 24 at 2:22
add a comment |
1
I don't have an exact answer, but in general I'd say that passing exceedingly large argument lists (where "exceedingly" is defined as upwards of a couple hundred) tends to cause strange behavior, since argument lists are often stored somewhere in memory that makes assumptions about their size. Some languages even define a maximum argument count explicitly; for example, in Common Lisp the variableCALL-ARGUMENTS-LIMIT
tells you the maximum number of arguments.
– Silvio Mayolo
Aug 24 at 2:22
1
1
I don't have an exact answer, but in general I'd say that passing exceedingly large argument lists (where "exceedingly" is defined as upwards of a couple hundred) tends to cause strange behavior, since argument lists are often stored somewhere in memory that makes assumptions about their size. Some languages even define a maximum argument count explicitly; for example, in Common Lisp the variable
CALL-ARGUMENTS-LIMIT
tells you the maximum number of arguments.– Silvio Mayolo
Aug 24 at 2:22
I don't have an exact answer, but in general I'd say that passing exceedingly large argument lists (where "exceedingly" is defined as upwards of a couple hundred) tends to cause strange behavior, since argument lists are often stored somewhere in memory that makes assumptions about their size. Some languages even define a maximum argument count explicitly; for example, in Common Lisp the variable
CALL-ARGUMENTS-LIMIT
tells you the maximum number of arguments.– Silvio Mayolo
Aug 24 at 2:22
add a comment |
2 Answers
2
active
oldest
votes
up vote
26
down vote
accepted
I think that this is not a recursion error, but an argument stack error. You are running up against the limit of the Ruby VM stack depth for arguments.
The problem is the splat operator, which is passed as an argument to push
. The splat operator is expanded into a million element argument list for push
.
As function arguments are passed as stack elements, and the pre-configured max size of the Ruby VM stack size is:
RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576
..this is where the limit comes from.
You can try the following:
RUBY_THREAD_VM_STACK_SIZE=10000000 ruby array_script.rb
..and it will work fine.
This is also the reason you want to use concat
instead, as the whole array can be passed as one single reference, and concat
will then process the array internally. As opposed to push
+ splat, which will try to use the stack as a temporary storage for all the array elements.
1
But why does[*a1]
work with default stack size? Is the call being optimized?
– Stefan
Aug 24 at 6:36
2
Because it's not a call, it's a literal. The argument list on the call stack doesn't overflow because there are no arguments, because there is no call.
– Jörg W Mittag
Aug 24 at 10:50
@JörgWMittag that makes sense, somehow. I expected it to fail in the same way because – although no Ruby method is being called – the underlying C code to create and fill an array out ofa1
's elements has to be invoked.
– Stefan
Aug 24 at 11:03
Yes, but probably with one argument, not with one million.
– Jörg W Mittag
Aug 24 at 11:48
add a comment |
up vote
2
down vote
Casper has already answered the question in the title and given you a solution you can use to make a1.push *a2
work, but I'd like to talk about the last question you asked, on whether it's a good idea.
More specifically, if you are going to work with arrays that are millions of items long in production code, performance becomes something to keep in mind. http://www.continuousthinking.com/2011/09/07/ruby_array_plus_vs_push.html has an overview of 4 different ways to handle array concatenation in ruby: +
, .push
, <<
and .concat
.
There they mention that array.push
will effectively handle each argument separately, and increase the array size by 50% every time the array is too small. This means that in your example, a
will be increased in size 2 times and get 1 million appends. Meanwhile, array.concat
will first calculate the new size of the array, extend the original array and then copy the new array into the right place.
For situations like yours, concat
will most likely be more performant, both from a memory and from a CPU usage perspective. However, without benchmarks I can't say for sure. My recommendation is to measure the time and memory usage to do both operations for the size of the arrays you want to handle. concat
will most likely come out on top, but I might be mistaken on that front.
A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
– Stefan
Aug 24 at 10:47
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51995480%2farraypush-causes-stack-level-too-deep-error-with-large-arrays%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
26
down vote
accepted
I think that this is not a recursion error, but an argument stack error. You are running up against the limit of the Ruby VM stack depth for arguments.
The problem is the splat operator, which is passed as an argument to push
. The splat operator is expanded into a million element argument list for push
.
As function arguments are passed as stack elements, and the pre-configured max size of the Ruby VM stack size is:
RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576
..this is where the limit comes from.
You can try the following:
RUBY_THREAD_VM_STACK_SIZE=10000000 ruby array_script.rb
..and it will work fine.
This is also the reason you want to use concat
instead, as the whole array can be passed as one single reference, and concat
will then process the array internally. As opposed to push
+ splat, which will try to use the stack as a temporary storage for all the array elements.
1
But why does[*a1]
work with default stack size? Is the call being optimized?
– Stefan
Aug 24 at 6:36
2
Because it's not a call, it's a literal. The argument list on the call stack doesn't overflow because there are no arguments, because there is no call.
– Jörg W Mittag
Aug 24 at 10:50
@JörgWMittag that makes sense, somehow. I expected it to fail in the same way because – although no Ruby method is being called – the underlying C code to create and fill an array out ofa1
's elements has to be invoked.
– Stefan
Aug 24 at 11:03
Yes, but probably with one argument, not with one million.
– Jörg W Mittag
Aug 24 at 11:48
add a comment |
up vote
26
down vote
accepted
I think that this is not a recursion error, but an argument stack error. You are running up against the limit of the Ruby VM stack depth for arguments.
The problem is the splat operator, which is passed as an argument to push
. The splat operator is expanded into a million element argument list for push
.
As function arguments are passed as stack elements, and the pre-configured max size of the Ruby VM stack size is:
RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576
..this is where the limit comes from.
You can try the following:
RUBY_THREAD_VM_STACK_SIZE=10000000 ruby array_script.rb
..and it will work fine.
This is also the reason you want to use concat
instead, as the whole array can be passed as one single reference, and concat
will then process the array internally. As opposed to push
+ splat, which will try to use the stack as a temporary storage for all the array elements.
1
But why does[*a1]
work with default stack size? Is the call being optimized?
– Stefan
Aug 24 at 6:36
2
Because it's not a call, it's a literal. The argument list on the call stack doesn't overflow because there are no arguments, because there is no call.
– Jörg W Mittag
Aug 24 at 10:50
@JörgWMittag that makes sense, somehow. I expected it to fail in the same way because – although no Ruby method is being called – the underlying C code to create and fill an array out ofa1
's elements has to be invoked.
– Stefan
Aug 24 at 11:03
Yes, but probably with one argument, not with one million.
– Jörg W Mittag
Aug 24 at 11:48
add a comment |
up vote
26
down vote
accepted
up vote
26
down vote
accepted
I think that this is not a recursion error, but an argument stack error. You are running up against the limit of the Ruby VM stack depth for arguments.
The problem is the splat operator, which is passed as an argument to push
. The splat operator is expanded into a million element argument list for push
.
As function arguments are passed as stack elements, and the pre-configured max size of the Ruby VM stack size is:
RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576
..this is where the limit comes from.
You can try the following:
RUBY_THREAD_VM_STACK_SIZE=10000000 ruby array_script.rb
..and it will work fine.
This is also the reason you want to use concat
instead, as the whole array can be passed as one single reference, and concat
will then process the array internally. As opposed to push
+ splat, which will try to use the stack as a temporary storage for all the array elements.
I think that this is not a recursion error, but an argument stack error. You are running up against the limit of the Ruby VM stack depth for arguments.
The problem is the splat operator, which is passed as an argument to push
. The splat operator is expanded into a million element argument list for push
.
As function arguments are passed as stack elements, and the pre-configured max size of the Ruby VM stack size is:
RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576
..this is where the limit comes from.
You can try the following:
RUBY_THREAD_VM_STACK_SIZE=10000000 ruby array_script.rb
..and it will work fine.
This is also the reason you want to use concat
instead, as the whole array can be passed as one single reference, and concat
will then process the array internally. As opposed to push
+ splat, which will try to use the stack as a temporary storage for all the array elements.
edited Aug 28 at 20:05
answered Aug 24 at 2:13
Casper
26.7k36664
26.7k36664
1
But why does[*a1]
work with default stack size? Is the call being optimized?
– Stefan
Aug 24 at 6:36
2
Because it's not a call, it's a literal. The argument list on the call stack doesn't overflow because there are no arguments, because there is no call.
– Jörg W Mittag
Aug 24 at 10:50
@JörgWMittag that makes sense, somehow. I expected it to fail in the same way because – although no Ruby method is being called – the underlying C code to create and fill an array out ofa1
's elements has to be invoked.
– Stefan
Aug 24 at 11:03
Yes, but probably with one argument, not with one million.
– Jörg W Mittag
Aug 24 at 11:48
add a comment |
1
But why does[*a1]
work with default stack size? Is the call being optimized?
– Stefan
Aug 24 at 6:36
2
Because it's not a call, it's a literal. The argument list on the call stack doesn't overflow because there are no arguments, because there is no call.
– Jörg W Mittag
Aug 24 at 10:50
@JörgWMittag that makes sense, somehow. I expected it to fail in the same way because – although no Ruby method is being called – the underlying C code to create and fill an array out ofa1
's elements has to be invoked.
– Stefan
Aug 24 at 11:03
Yes, but probably with one argument, not with one million.
– Jörg W Mittag
Aug 24 at 11:48
1
1
But why does
[*a1]
work with default stack size? Is the call being optimized?– Stefan
Aug 24 at 6:36
But why does
[*a1]
work with default stack size? Is the call being optimized?– Stefan
Aug 24 at 6:36
2
2
Because it's not a call, it's a literal. The argument list on the call stack doesn't overflow because there are no arguments, because there is no call.
– Jörg W Mittag
Aug 24 at 10:50
Because it's not a call, it's a literal. The argument list on the call stack doesn't overflow because there are no arguments, because there is no call.
– Jörg W Mittag
Aug 24 at 10:50
@JörgWMittag that makes sense, somehow. I expected it to fail in the same way because – although no Ruby method is being called – the underlying C code to create and fill an array out of
a1
's elements has to be invoked.– Stefan
Aug 24 at 11:03
@JörgWMittag that makes sense, somehow. I expected it to fail in the same way because – although no Ruby method is being called – the underlying C code to create and fill an array out of
a1
's elements has to be invoked.– Stefan
Aug 24 at 11:03
Yes, but probably with one argument, not with one million.
– Jörg W Mittag
Aug 24 at 11:48
Yes, but probably with one argument, not with one million.
– Jörg W Mittag
Aug 24 at 11:48
add a comment |
up vote
2
down vote
Casper has already answered the question in the title and given you a solution you can use to make a1.push *a2
work, but I'd like to talk about the last question you asked, on whether it's a good idea.
More specifically, if you are going to work with arrays that are millions of items long in production code, performance becomes something to keep in mind. http://www.continuousthinking.com/2011/09/07/ruby_array_plus_vs_push.html has an overview of 4 different ways to handle array concatenation in ruby: +
, .push
, <<
and .concat
.
There they mention that array.push
will effectively handle each argument separately, and increase the array size by 50% every time the array is too small. This means that in your example, a
will be increased in size 2 times and get 1 million appends. Meanwhile, array.concat
will first calculate the new size of the array, extend the original array and then copy the new array into the right place.
For situations like yours, concat
will most likely be more performant, both from a memory and from a CPU usage perspective. However, without benchmarks I can't say for sure. My recommendation is to measure the time and memory usage to do both operations for the size of the arrays you want to handle. concat
will most likely come out on top, but I might be mistaken on that front.
A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
– Stefan
Aug 24 at 10:47
add a comment |
up vote
2
down vote
Casper has already answered the question in the title and given you a solution you can use to make a1.push *a2
work, but I'd like to talk about the last question you asked, on whether it's a good idea.
More specifically, if you are going to work with arrays that are millions of items long in production code, performance becomes something to keep in mind. http://www.continuousthinking.com/2011/09/07/ruby_array_plus_vs_push.html has an overview of 4 different ways to handle array concatenation in ruby: +
, .push
, <<
and .concat
.
There they mention that array.push
will effectively handle each argument separately, and increase the array size by 50% every time the array is too small. This means that in your example, a
will be increased in size 2 times and get 1 million appends. Meanwhile, array.concat
will first calculate the new size of the array, extend the original array and then copy the new array into the right place.
For situations like yours, concat
will most likely be more performant, both from a memory and from a CPU usage perspective. However, without benchmarks I can't say for sure. My recommendation is to measure the time and memory usage to do both operations for the size of the arrays you want to handle. concat
will most likely come out on top, but I might be mistaken on that front.
A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
– Stefan
Aug 24 at 10:47
add a comment |
up vote
2
down vote
up vote
2
down vote
Casper has already answered the question in the title and given you a solution you can use to make a1.push *a2
work, but I'd like to talk about the last question you asked, on whether it's a good idea.
More specifically, if you are going to work with arrays that are millions of items long in production code, performance becomes something to keep in mind. http://www.continuousthinking.com/2011/09/07/ruby_array_plus_vs_push.html has an overview of 4 different ways to handle array concatenation in ruby: +
, .push
, <<
and .concat
.
There they mention that array.push
will effectively handle each argument separately, and increase the array size by 50% every time the array is too small. This means that in your example, a
will be increased in size 2 times and get 1 million appends. Meanwhile, array.concat
will first calculate the new size of the array, extend the original array and then copy the new array into the right place.
For situations like yours, concat
will most likely be more performant, both from a memory and from a CPU usage perspective. However, without benchmarks I can't say for sure. My recommendation is to measure the time and memory usage to do both operations for the size of the arrays you want to handle. concat
will most likely come out on top, but I might be mistaken on that front.
Casper has already answered the question in the title and given you a solution you can use to make a1.push *a2
work, but I'd like to talk about the last question you asked, on whether it's a good idea.
More specifically, if you are going to work with arrays that are millions of items long in production code, performance becomes something to keep in mind. http://www.continuousthinking.com/2011/09/07/ruby_array_plus_vs_push.html has an overview of 4 different ways to handle array concatenation in ruby: +
, .push
, <<
and .concat
.
There they mention that array.push
will effectively handle each argument separately, and increase the array size by 50% every time the array is too small. This means that in your example, a
will be increased in size 2 times and get 1 million appends. Meanwhile, array.concat
will first calculate the new size of the array, extend the original array and then copy the new array into the right place.
For situations like yours, concat
will most likely be more performant, both from a memory and from a CPU usage perspective. However, without benchmarks I can't say for sure. My recommendation is to measure the time and memory usage to do both operations for the size of the arrays you want to handle. concat
will most likely come out on top, but I might be mistaken on that front.
edited Aug 24 at 20:25
dinjas
1,6771422
1,6771422
answered Aug 24 at 8:14
Nzall
2,93031849
2,93031849
A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
– Stefan
Aug 24 at 10:47
add a comment |
A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
– Stefan
Aug 24 at 10:47
A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
– Stefan
Aug 24 at 10:47
A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
– Stefan
Aug 24 at 10:47
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51995480%2farraypush-causes-stack-level-too-deep-error-with-large-arrays%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
I don't have an exact answer, but in general I'd say that passing exceedingly large argument lists (where "exceedingly" is defined as upwards of a couple hundred) tends to cause strange behavior, since argument lists are often stored somewhere in memory that makes assumptions about their size. Some languages even define a maximum argument count explicitly; for example, in Common Lisp the variable
CALL-ARGUMENTS-LIMIT
tells you the maximum number of arguments.– Silvio Mayolo
Aug 24 at 2:22