Comments 12
Про SIMD можно найти прямо вашу задачу на StackOverflow. Хотя, кажется, можно проще, чем там предлагают, если использовать __mm_shuffle_epi8.
Но не уверен, что это сильно поможет - возможно, вы уже приблизились к границе пропускной способности памяти
Развивая предложения чатгопоты про SIMD, вот статья где разжевано как AVX512 прикрутить в Go через C/C++ и ассемблер:
https://gorse.io/posts/avx512-in-golang.html#function-calls
Только вместо MUL который они используют в статье, вам видимо надо что-то вроде VPMOVUSQD.
Это всё здорово. Только зачем? Пишите сразу на ASM.
А вы пробовали на нем писать, что-то больше пары строчек? А потом еще и поддерживать? Странный совет, на вполне нормальное желание разобраться, как работает код и как его можно ускорить. Если писать что-то производительное на плюсах, то это вообще первым пунктом идет - посмотреть асм выхлоп и понять его.
Вот эту портянку на "go" тоже поддерживать сложно.
Сложно по сравнению с чем? С биндингами на Си(или как тут посоветовали ассемблере)? Или не оптимизированным кодом, дающем в три раза меньшую производительность? ("извините но у нас лапки")
Да и называть "портянкой" дополнительные 6 строк кода, в которых даже для меня, человека не знакомого с go, очевидно что происходит, довольно странно.
И я как бы исхожу из предположения, об адекватности автора статьи, и то что это не бессмысленный premature optimization, а реальный хотспот. И в обработке изображений и растеризации такое сплошь и рядом.
Я скорее имел в виду, что на го писать в таком стиле не особо принято. Это язык прикладного применения, его прелесть в простоте. Для решения подобных низкоуровневых задач с кучей ручных оптимизаций обычно используют си или плюсы.
Поэтому да, на любом языке это будет выглядеть сложно. Применять именно го для этого странно.
Пробовал. Прекрасно на нём пишется. В том числе крупные проекты.
Думаете люди до изобретения Go не писали ничего, сложнее калькуляторов? ?
Вы же сами подглядываете в то, что для вас Go компилирует, так почему сразу не сделать как хочется?
Ссылку на гитхаб дадите? Или расскажите, что вам еще за это и деньги платили? )
Вы же сами подглядываете в то, что для вас Go компилирует, так почему сразу не сделать как хочется?
Потому что даже "просто сделать", сложнее. Не говоря о том, что это еще и поддерживать надо, а это сложнее еще на порядок. А компиляторы уже давно компилируют субоптимальный код, если им не мешать, или понять что мешает.
Вместо преобразования указателя к uintptr лучше использовать https://pkg.go.dev/unsafe#Add (и в описании к Ponter сказано, почему)
Получится примерно такое
dstAddr := unsafe.Pointer(&dstPaletted.Pix[0])
srcAddr := unsafe.Add(unsafe.Pointer(&srcRGBA.Pix[0])), 2)
for y := 0; y < imageHeight; y++ {
for x := 0; x < imageWidth; x++ {
*(*uint8)(dstAddr) = *(*uint8)(srcAddr)
dstAddr = unsafe.Add(dstAddr, 1)
srcAddr = unsafe.Add(srcAddr, 4)
}
}
А вообще, проблеме с удалением bounds check уже немало лет, https://github.com/golang/go/issues/14808
Странно, но этот вариант выигрывает у CopyUnsafeV2
BenchmarkCopyUnsafeV2-16 68326 84459 ns/op 0 B/op 0 allocs/op
BenchmarkCopyUnsafeV3-16 80085 75279 ns/op 0 B/op 0 allocs/op
, но проигрывает в Unrolled варианте:
BenchmarkCopyUnrolledLoop4-16 103786 55923 ns/op 0 B/op 0 allocs/op
BenchmarkCopyUnrolledLoop4v2-16 96066 61919 ns/op 0 B/op 0 allocs/op
Хотя ассемблер выглядит чище ?
А попробуйте ещё такие варианты:
func BenchmarkCopySliceElementsV2(b *testing.B) {
for i := 0; i < b.N; i++ {
dst := dstPaletted.Pix
src := srcRGBA.Pix
dstAddr := imageWidth*imageHeight - 4
srcAddr := imageWidth*imageHeight*4 - 16
_ = dst[dstAddr+3]
_ = src[srcAddr+12]
for dstAddr >= 0 {
dst[dstAddr+0] = src[srcAddr+0]
dst[dstAddr+1] = src[srcAddr+4]
dst[dstAddr+2] = src[srcAddr+8]
dst[dstAddr+3] = src[srcAddr+12]
dstAddr -= 4
srcAddr -= 16
}
}
}
func BenchmarkCopyUnrolledLoop4V2(b *testing.B) {
for i := 0; i < b.N; i++ {
dstAddr := unsafe.Pointer(&dstPaletted.Pix[0])
srcAddr := unsafe.Pointer(&srcRGBA.Pix[0])
dstAddrMax := unsafe.Add(dstAddr, imageHeight*imageWidth)
for uintptr(dstAddr) < uintptr(dstAddrMax) {
*(*uint32)(dstAddr) = *(*uint32)(unsafe.Add(srcAddr, 0)) |
(*(*uint32)(unsafe.Add(srcAddr, 4)) << 8) |
(*(*uint32)(unsafe.Add(srcAddr, 8)) << 16) |
(*(*uint32)(unsafe.Add(srcAddr, 12)) << 32)
dstAddr = unsafe.Add(dstAddr, 4)
srcAddr = unsafe.Add(srcAddr, 16)
}
}
}
Оптимизация доступа к элементам слайса в Go