Есть ли аналог memset в go?

В С++ я могу инициализировать массив некоторым значением, используя набор памяти:

const int MAX = 1000000;
int is_prime[MAX]

memset(is_prime, 1, sizeof(is_prime))

То, что делает memset, грубо можно описать как заполнение массива некоторым значением, но делает это очень-очень быстро.

В go я могу сделать is_prime := make([]int, 1000000), но это создаст срез со всеми 0, аналогичным образом я могу использовать new([1000000]int), но ничто не позволит мне создать массив/срез со всеми 1 или любым другим ненулевым элементом.

Конечно, я могу использовать цикл, чтобы заполнить его значением позже, но основная цель memset заключается в том, что это намного быстрее, чем цикл.

Так есть ли у программистов Go аналог memset (быстрый способ инициализации массива некоторым ненулевым значением)?


person Salvador Dali    schedule 03.06.2015    source источник
comment
Вы можете использовать runtime.duffzero с другим значением, кроме 0 в АКС. См. также mkduff.go.   -  person thwd    schedule 03.06.2015
comment
С любым хорошим компилятором цикл работает так же быстро, как memset   -  person edc65    schedule 03.06.2015
comment
Ваш memset также не инициализирует все элементы в 1.   -  person milleniumbug    schedule 03.06.2015
comment
Просто из любопытства, есть ли у вас тайминги производительности memset vs цикла?   -  person Akavall    schedule 03.06.2015
comment
@ edc65 это распространенный миф, что цикл будет таким же быстрым, как memset. В современном мире со специальным управлением кешем и SIMD-инструкциями memset может работать значительно быстрее на больших буферах, чем любой простой цикл скалярных регистров. Это началось с PowerPC в 90-х годах и перешло к Intel с Pentium III, а также верно для ARM с инструкциями NEON. При этом: компилятор go явно НЕ тратит много времени на оптимизацию, потому что он должен отдавать приоритет быстрому времени компиляции.   -  person Jon Watte    schedule 01.03.2020


Ответы (2)


Самое простое решение с циклом будет выглядеть так:

func memsetLoop(a []int, v int) {
    for i := range a {
        a[i] = v
    }
}

В стандартной библиотеке нет поддержки memset, но мы можем использовать встроенную высокооптимизированную copy(). .

С повторяющимся copy()

Мы можем установить первый элемент вручную и начать копирование уже установленной части в неустановленную с помощью copy(); где уже заданная часть с каждым разом становится все больше и больше (удваивается), поэтому количество итераций равно log(n):

func memsetRepeat(a []int, v int) {
    if len(a) == 0 {
        return
    }
    a[0] = v
    for bp := 1; bp < len(a); bp *= 2 {
        copy(a[bp:], a[:bp])
    }
}

Это решение было вдохновлено реализацией bytes.Repeat(). Если вы просто хотите создать новый []byte, заполненный теми же значениями, вы можете использовать функцию bytes.Repeat(). Вы не можете использовать это для существующего фрагмента или фрагментов, отличных от []byte, для этого вы можете использовать представленный memsetRepeat().

В случае небольших фрагментов memsetRepeat() может быть медленнее, чем memsetLoop() (но в случае небольших фрагментов это не имеет большого значения, он будет работать в одно мгновение).

Из-за использования быстрого copy(), memsetRepeat() будет намного быстрее, если количество элементов увеличится.

Сравнение этих двух решений:

var a = make([]int, 1000) // Size will vary

func BenchmarkLoop(b *testing.B) {
    for i := 0; i < b.N; i++ {
        memsetLoop(a, 10)
    }
}

func BenchmarkRepeat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        memsetRepeat(a, 11)
    }
}

Сравнительные результаты

100 элементов: примерно в 1,15 раза быстрее

BenchmarkLoop   20000000                81.6 ns/op
BenchmarkRepeat 20000000                71.0 ns/op

1000 элементов: примерно в 2,5 раза быстрее

BenchmarkLoop    2000000               706 ns/op
BenchmarkRepeat  5000000               279 ns/op

10 000 элементов: примерно в 2 раза быстрее

BenchmarkLoop     200000              7029 ns/op
BenchmarkRepeat   500000              3544 ns/op

100 000 элементов: примерно в 1,5 раза быстрее

BenchmarkLoop      20000             70671 ns/op
BenchmarkRepeat    30000             45213 ns/op

Наибольший прирост производительности составляет около 3800-4000 элементов, что в примерно в 3,2 раза быстрее.

person icza    schedule 03.06.2015
comment
Спасибо за ответ. Выглядит странно, что теоретически можно получить log(n), а практически только примерно в 2 раза быстрее. - person Salvador Dali; 03.06.2015
comment
@SalvadorDali Количество итераций равно log(n), но каждая copy() в последующей итерации должна выполнять в два раза больше работы (копировать в два раза больше элементов). Так что дело не только в количестве итераций. - person icza; 03.06.2015
comment
Небольшое примечание: в компиляторе Go существует эта оптимизация. - person Awn; 10.05.2018
comment
Запустите на Linux 3.10.0 с amd64 go 1.14 и результат: goos: linux goarch: amd64 pkg: - BenchmarkLoop-4 1949619 573 ns/op BenchmarkRepeat-4 785382 1359 ns/op - person Liqang Lau; 09.03.2020

Согласно этой ошибке под названием "оптимизировать идиому memset", в Go нет другого способа сделать это, кроме как с помощью цикла. . Тема закрыта 9 января 2013 этим сообщением

Я считаю это исправленным. Оптимизация ненулевых случаев не очень интересна.

Мы можем открыть еще одну ошибку, если люди сильно захотят делать больше.

Таким образом, решение состоит в том, чтобы использовать цикл, уже описанный в icza.

Существует bytes.Repeat, но он также просто использует цикл:

func Repeat(b []byte, count int) []byte {
    nb := make([]byte, len(b)*count)
    bp := copy(nb, b)
    for bp < len(nb) {
        copy(nb[bp:], nb[:bp])
        bp *= 2
    }
    return nb
}
person IamNaN    schedule 03.06.2015
comment
Обратите внимание, что реализация C также является просто циклом. Только он гарантирует, что он выровнен по словам и пишет слово за словом, а не ширину рассматриваемого типа данных. - person thwd; 03.06.2015
comment
В C он, вероятно, будет скомпилирован в конкретную инструкцию на x86, так что на самом деле это не цикл, т.е. компилятор, встроенный в memset - person paulm; 03.06.2015