Всем привет. У каждого, кто знаком с Go был вопрос, а какого лешего я не могу сделать вот так вот:


var  a = []int{1,2,3}
b = []interface(a)

Почему приходится писать вот так:


b := make([]interface{}, len(a))
for i:=0; i<len(a); i++ {
   b[i] = a[i]
}

Но, можно и по другому....


Я решил немного размяться и найти способ сделать это быстрее и удобнее. Получилось.


Подробности здесь.


Если коротко, то благодаря пакету unsafe удалось заменить создание слайса интерфейсов и копирование данных, созданием слайса интерфейсов с len и cap оригинального слайса и копирование указателей. Благодаря особенностям реализации интерфейсов, получился, в нагрзуку, бесплатный (но не полноценный) механизм COW (copy-on-write): пока вы не изменяете данные в итоговом слайсе данные в исходном и результирующем слайсе лежат в одном месте, но при изменении элементов результирующего слайса этот элемент уже не будет указывать на исходный. В обратном порядке, к сожалению, это не работает.


Немножко о производительности:


Эффективность решения напрямую зависит от размера элементов слайса и длинны слайса, чем больше длинна слайса и "вес" элемента — тем эффективнее


https://gist.github.com/t0pep0/af41fba259eb4d3d00d2e7efa0e4093a

Поделиться с друзьями
-->

Комментарии (5)


  1. Shtucer
    21.04.2017 23:31
    +1

    Ничего не понял.
    "Но можно и по другому" и ещё десятком способов, ну или меньше. Чем же предложенный метод лучше очевидного? Или это какой-то очередной плач про отсутствие дженериков? Это, как мне кажется не лучшее применение для uniptr.


    1. t0pep0
      22.04.2017 13:50

      Это просто интересный «ещё один» способ сделать так. Лучше очевидно — меньшим количеством аллокаций и большей скоростью выполнения. Но я бы не рекомендовал использовать подобное в продакшене.
      Это не в коем случае не плач про дженерики.
      Лично для меня это был не большой челлендж, смогу или нет — как итог — с удовольствием поковырялся в исходниках Go и его рантайме


  1. youROCK
    22.04.2017 00:22
    +7

    Тут на самом деле авторы go как бы пытались намекнуть — поскольку memory layout у слайса интерфейсов и у слайса интов разный, то они друг к другу не кастуются за O(1), поэтому автоматического преобразования нет. То есть, по возможности (т.е. всегда) следует принимать слайс именно того типа, который вам нужен, или же принимать просто interface{} и итерироваться по нему (или забирать только нужные элементы из слайса) с помощью reflection. Первое намного предпочтительнее, потому что очевидно будет работать сильно быстрее :).


    1. t0pep0
      22.04.2017 13:56
      +1

      Да, Вы абсолютно правы.Могу даже дополнить, что если надо скастовать слайс в слайс интерфейсов, то с большей долей вероятности — что то не так.
      В данном случае int не при чём, вместо int'а там мог быть слайс строк или булов, или своих структур, для этого надо изменить только само определения слайса.
      Данный метод быстрее, чем перебор циклом и гораздо быстрее, чем рефлексия, но я, как уже говорил выше, не стал бы его использовать в продакшене.


      1. divan0
        23.04.2017 18:51

        Напомнило README к этому проекту:


        • Должен ли я это использовать?
        • Конечно же да! Если таки будете, то напишите мне, какая ужасная идея заставила вас так подумать. Мне очень интересно. :)
          https://github.com/zeebo/goof