Pull to refresh

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)
		}
	}
}

Sign up to leave a comment.

Articles