There is an issue and CL for this ("this" = keeping the backing store alive 
when cloning a zero-length slice). See:

https://github.com/golang/go/issues/68488
https://go-review.googlesource.com/c/go/+/598875

On Thursday, September 26, 2024 at 6:59:49 AM UTC-7 Brian Candler wrote:

> FWIW, I found this thread:
> https://groups.google.com/g/golang-nuts/c/QFiyvup5UuY/m/xBalv9Y3CAAJ
> which says experimentally (albeit without showing code or results) that
>     append(S(nil), s...)
>
>     append(s[:0:0], s...)
> are faster than some other methods of cloning a slice, when s is "large".
>
> On Thursday 26 September 2024 at 14:55:22 UTC+1 Brian Candler wrote:
>
>> > I want to know the reason behind the decision of using *append(s[:0:0], 
>> s...)* over the previous code
>>
>> It would be helpful to identify specifically the "previous code" you're 
>> comparing against.  Looking in git history I find this commit from about a 
>> year ago:
>>
>> commit b581e447394b4ba7a08ea64b214781cae0f4ef6c
>> Author: Brad Fitzpatrick <brad...@golang.org>
>> Date:   Sat Aug 19 09:08:38 2023 -0700
>>
>>     slices: simplify Clone a bit
>>
>>     No need for an explicit nil check. Slicing the input slice
>>     down to zero capacity also preserves nil.
>>
>>     Change-Id: I1f53cc485373d0e65971cd87b6243650ac72612c
>>     Reviewed-on: https://go-review.googlesource.com/c/go/+/521037
>>     Run-TryBot: Brad Fitzpatrick <brad...@golang.org>
>>     Reviewed-by: Dmitri Shuralyov <dmit...@google.com>
>>     TryBot-Result: Gopher Robot <go...@golang.org>
>>     Reviewed-by: Ian Lance Taylor <ia...@google.com>
>>
>> diff --git a/src/slices/slices.go b/src/slices/slices.go
>> index a4d9f7e3f5..252a8eecfc 100644
>> --- a/src/slices/slices.go
>> +++ b/src/slices/slices.go
>> @@ -333,11 +333,8 @@ func Replace[S ~[]E, E any](s S, i, j int, v ...E) S 
>> {
>>  // Clone returns a copy of the slice.
>>  // The elements are copied using assignment, so this is a shallow clone.
>>  func Clone[S ~[]E, E any](s S) S {
>> -       // Preserve nil in case it matters.
>> -       if s == nil {
>> -               return nil
>> -       }
>> -       return append(S([]E{}), s...)
>> +       // The s[:0:0] preserves nil in case it matters.
>> +       return append(s[:0:0], s...)
>>  }
>>
>> Is that the change you're referring to?
>>
>> The comment says that "slicing the input slice down to zero capacity also 
>> preserves nil", which I confirm:
>> https://go.dev/play/p/W21qUffeSpg
>>
>> Therefore, it's an explicit goal of the code to preserve nilness ("in 
>> case it matters"), which your alternative of *append(S(nil), s...)* would 
>> not do. I do agree that for most practical purposes a nil slice and a 
>> zero-length, zero-capacity slice are more or less interchangeable, but it 
>> *is* possible to distinguish them:
>> https://go.dev/play/p/Irxuq6pbv4X
>> ... and therefore some code might depend on this (perhaps a serialization 
>> library?). It's user-visible, so it's safest to clone like with like.
>>
>> Apart from that, your issue seems to be: cloning an empty slice with the 
>> new code keeps a reference to the original slice backing array, albeit with 
>> zero len and cap. It can never overwrite the original backing slice, but it 
>> *can* prevent the original backing array being freed. Is that a correct 
>> summary? 
>>
>> It seems to me that it would be pretty perverse to take a large slice, 
>> slice it down to zero len, and then ask for it to be cloned; your example 
>> doesn't seem like a real-world use case.
>>
>> On Thursday 26 September 2024 at 13:29:59 UTC+1 Hikmatulloh Hari Mukti 
>> (Hari) wrote:
>>
>>> Hi gophers, I want to know the reason behind the decision of using 
>>> *append(s[:0:0], 
>>> s...)* over the previous code since the two code return different slice 
>>> when dealing slice with zero len. The previous code will return brand new 
>>> slice with size zero, while the current code return an empty slice that's 
>>> still pointing to the previous array. And also, why not maybe using 
>>> *append(S(nil), 
>>> s...)* instead? This will return nil when dealing with zero len slice 
>>> though, but what's the potential problem that it will cause?
>>>
>>> I don't know if this can be considered for a problem, but here is my 
>>> concern for the current code, *append(s[:0:0], s...)* :
>>>
>>> If we try to create slices from an array pool to reduce allocation by 
>>> using append, and many our slices turned out to be zero, slices.Clone will 
>>> return slice that still pointing to array in the pool. If we try creating 
>>> many of them concurrently, (if I understand it correctly) the pool may try 
>>> to create many array objects as the object retrieved from Get may haven't 
>>> been Put back to the pool. Those array objects can only be 
>>> garbage-collected after those slices are no longer used / reachable and if 
>>> it's an array of a big struct, wouldn't it might potentially pressure the 
>>> memory?
>>>
>>> Here is just a pseudo-code for illustration only. I think the array 
>>> generated by pool will only be garbage-collected once *ch* is consumed 
>>> and the slices are no longer used:
>>>
>>> var pool = sync.Pool{New: func() any { return &[255]bigstruct{} }}
>>> var ch = make(chan []bigstruct, 1000)
>>> for i := 0; i < 1000; i++ {
>>> go func() {
>>> arr := pool.Get().(*[255]bigstruct)
>>> defer pool.Put(arr)
>>> s := arr[:0]
>>> ch <- slices.Clone(s) // slice points to arr
>>> }()
>>> }
>>>
>>>
>>> CMIIW and thank you!
>>>
>>>
>>>
>>>

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/ce5fc4db-c4d9-46c9-8e64-c63afd3ac6den%40googlegroups.com.

Reply via email to