Array#push causes “stack level too deep” error with large arrays









up vote
25
down vote

favorite
1












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?










share|improve this question



















  • 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














up vote
25
down vote

favorite
1












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?










share|improve this question



















  • 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












up vote
25
down vote

favorite
1









up vote
25
down vote

favorite
1






1





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?










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 variable CALL-ARGUMENTS-LIMIT tells you the maximum number of arguments.
    – Silvio Mayolo
    Aug 24 at 2:22












  • 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







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












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.






share|improve this answer


















  • 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 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

















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.






share|improve this answer






















  • A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
    – Stefan
    Aug 24 at 10:47










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
);



);













draft saved

draft discarded


















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.






share|improve this answer


















  • 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 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














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.






share|improve this answer


















  • 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 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












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.






share|improve this answer














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.







share|improve this answer














share|improve this answer



share|improve this answer








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 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












  • 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 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







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












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.






share|improve this answer






















  • A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
    – Stefan
    Aug 24 at 10:47














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.






share|improve this answer






















  • A while ago I asked How to efficiently concatenate multiple arrays in Ruby? – the answers also have benchmarks.
    – Stefan
    Aug 24 at 10:47












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.






share|improve this answer














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.







share|improve this answer














share|improve this answer



share|improve this answer








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
















  • 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

















draft saved

draft discarded
















































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.




draft saved


draft discarded














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





















































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







Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)