FAQ
I don't really pretend to know enough about the internals of Go to fully
explain these benchmarks or to see if there are any flaws lurking in here.
  But I went to the trouble to put this stuff together (for my own
curiosity) so I figured it was at least worth sharing.

The main question I was seeking an answer to was whether copy(y, x) was
faster than y = append([]type{}, x...)

I did this benchmark in two stages. Here is the code and discussion for
the first version:

https://gist.github.com/xogeny/b819af6a0cf8ba1caaef

I dug a bit deeper. These results are a little confusing because of the
outlier case but seem to basically show the same results:

https://gist.github.com/xogeny/c8dfd0c4742aee502926

I'm posting these mainly for people to identify and comment on any flaws in
the code and/or discussion.

--
Mike


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Search Discussions

  • Axel Wagner at Jan 27, 2015 at 7:54 pm
    without doing to much cross-checking (I only use the second link for
    reference), I also don't know a lot:
    • You include the creation of the array in the timings. Use
       b.ResetTimer()
    • I think you might also run into trouble with
       compiler-optimization. e.g. in BenchmarkAppendAllocInline{,NoFunc} I
       would think it is a valid optimization, to just do nothing at all in
       the loop (you don't do anything with init, and it is easily proven
       that it does not escape the loop, so there really is no need to change
       it at all). Check the generated assembly.
    • You also include the make([]int64,…) in line 54 in the timings, which
       might or might not skew your result of preallocating
       vs. allocating. Then again, a literal compiler would need to
       initialize an array created with make to zeros, whereas the
       append-call doesn't need to do that… But then again, that should make
       the non-preallocate-method faster, not slower.
    • I also think, that 1000 is a relatively small number. Have you
       experimented with larger numbers? That might (or might not) also skew
       your numbers or make them more random.

    These are just random guesses though. Microbenchmarks like this are hard
    to do right, imho, because unexpected side-effects like optimization
    come stand in your way…

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Michael Tiller at Jan 27, 2015 at 8:32 pm
    First, thanks for commenting on this. Your comments raise some good points.
    On Tuesday, January 27, 2015 at 2:54:22 PM UTC-5, Axel Wagner wrote:

    without doing to much cross-checking (I only use the second link for
    reference), I also don't know a lot:
    • You include the creation of the array in the timings. Use
    b.ResetTimer()

    Note the array is created once and the copy is done b.N times. So this
    should be minor. But I did add this to the benchmark. It didn't make any
    difference regarding the conclusions.

    • I think you might also run into trouble with
    compiler-optimization. e.g. in BenchmarkAppendAllocInline{,NoFunc} I
    would think it is a valid optimization, to just do nothing at all in
    the loop (you don't do anything with init, and it is easily proven
    that it does not escape the loop, so there really is no need to change
    it at all). Check the generated assembly.
    I'm less interested in those cases, but if I crank the size of the slices
    up to 10000, they fall in line with the results for the other cases. So it
    doesn't seem likely it is being optimized away. I did worry about the same
    issue though.

    • You also include the make([]int64,…) in line 54 in the timings, which
    might or might not skew your result of preallocating
    vs. allocating. Then again, a literal compiler would need to
    initialize an array created with make to zeros, whereas the
    append-call doesn't need to do that… But then again, that should make
    the non-preallocate-method faster, not slower.
    I'm only setting the capacity, not the size. So there shouldn't be any
    initialization overhead...there is nothing to initialize.

    • I also think, that 1000 is a relatively small number. Have you
    experimented with larger numbers? That might (or might not) also skew
    your numbers or make them more random.
    I cranked it up to 10000. I also added the b.ResetTimer(). The basic
    conclusion still holds. Using copy is about the same as doing
    append([]int64{}, x...)

    The specific cases I'm interested in were:

    BenchmarkAppendAlloc 10000 203140 ns/op
    BenchmarkCopy 10000 207800 ns/op


    So the append version is slightly faster in this case.

    These are just random guesses though. Microbenchmarks like this are hard
    to do right, imho, because unexpected side-effects like optimization
    come stand in your way…
    But the arrays are created out of scope (and given non-zero values) so I
    don't see how anything could be optimized away (except in the cases you
    pointed out where the result is thrown away).

    So my conclusion would still be (after these expanded tests) that it
    doesn't really matter whether you use copy or append.


    --
    Mike

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Michael Tiller at Jan 27, 2015 at 8:33 pm
    Minor correction on this post. I cranked things up to 100,000, not 10,000.
    On Tuesday, January 27, 2015 at 3:32:48 PM UTC-5, Michael Tiller wrote:

    First, thanks for commenting on this. Your comments raise some good
    points.
    On Tuesday, January 27, 2015 at 2:54:22 PM UTC-5, Axel Wagner wrote:

    without doing to much cross-checking (I only use the second link for
    reference), I also don't know a lot:
    • You include the creation of the array in the timings. Use
    b.ResetTimer()

    Note the array is created once and the copy is done b.N times. So this
    should be minor. But I did add this to the benchmark. It didn't make any
    difference regarding the conclusions.

    • I think you might also run into trouble with
    compiler-optimization. e.g. in BenchmarkAppendAllocInline{,NoFunc} I
    would think it is a valid optimization, to just do nothing at all in
    the loop (you don't do anything with init, and it is easily proven
    that it does not escape the loop, so there really is no need to change
    it at all). Check the generated assembly.
    I'm less interested in those cases, but if I crank the size of the slices
    up to 10000, they fall in line with the results for the other cases. So it
    doesn't seem likely it is being optimized away. I did worry about the same
    issue though.

    • You also include the make([]int64,…) in line 54 in the timings, which
    might or might not skew your result of preallocating
    vs. allocating. Then again, a literal compiler would need to
    initialize an array created with make to zeros, whereas the
    append-call doesn't need to do that… But then again, that should make
    the non-preallocate-method faster, not slower.
    I'm only setting the capacity, not the size. So there shouldn't be any
    initialization overhead...there is nothing to initialize.

    • I also think, that 1000 is a relatively small number. Have you
    experimented with larger numbers? That might (or might not) also skew
    your numbers or make them more random.
    I cranked it up to 10000. I also added the b.ResetTimer(). The basic
    conclusion still holds. Using copy is about the same as doing
    append([]int64{}, x...)

    The specific cases I'm interested in were:

    BenchmarkAppendAlloc 10000 203140 ns/op
    BenchmarkCopy 10000 207800 ns/op


    So the append version is slightly faster in this case.

    These are just random guesses though. Microbenchmarks like this are hard
    to do right, imho, because unexpected side-effects like optimization
    come stand in your way…
    But the arrays are created out of scope (and given non-zero values) so I
    don't see how anything could be optimized away (except in the cases you
    pointed out where the result is thrown away).

    So my conclusion would still be (after these expanded tests) that it
    doesn't really matter whether you use copy or append.


    --
    Mike
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Axel Wagner at Jan 27, 2015 at 8:52 pm
    Hi,

    Michael Tiller <michael.tiller@gmail.com> writes:
    I'm only setting the capacity, not the size. So there shouldn't be any
    initialization overhead...there is nothing to initialize.
    Subslicing can access the underlying array out of the bounds of the
    slice itself and obviously you can't check on every subslicing operation
    if the array is initialized and do a lazy initialization (because
    performance). So, the underlying array is initialized to zero, even if
    the slice you take on it only covers parts of the array. e.g. in
    http://play.golang.org/p/HVMhKRypHF
    the underlying array is clearly initialized to zero.
    But the arrays are created out of scope (and given non-zero values) so I
    don't see how anything could be optimized away (except in the cases you
    pointed out where the result is thrown away).
    Well, for example the compiler could inline doCopy, do constant folding
    and decide, that it does not really need to do anything in the other
    cases either. Not saying that this is happening, just that it might and
    it is hard to say for sure, without looking at the generated machine
    code. :)
    So my conclusion would still be (after these expanded tests) that it
    doesn't really matter whether you use copy or append.
    Ah, in that I would agree. Apparently I missed that this was the point,
    sorry. More generally you shouldn't worry about stuff like this, until
    you have a real-world application with performance problems and it turns
    out that one or the other is a bottleneck. :) The implementations of
    append and copy clearly fall under "trust the compiler to do the right
    thing, until proven otherwise" :)

    Best,

    Axel

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupgolang-nuts @
categoriesgo
postedJan 27, '15 at 6:30p
activeJan 27, '15 at 8:52p
posts5
users2
websitegolang.org

2 users in discussion

Michael Tiller: 3 posts Axel Wagner: 2 posts

People

Translate

site design / logo © 2021 Grokbase