Как стать автором
Обновить
13
0

Пишу код

Отправить сообщение

Говоря про zap стоит раскрыть тему sampling-а

Wildberries — это крутой магазин, в котором работает очень много хороших специалистов. И владельцы, как бы спорно они не управляли, управляют очень эффективно. Тем не менее, по моим сведениям, Татьяна описала "оптимизацию", которая коснулась и её отдела, достаточно точно.

Вот что я придумал, мы:
1. создаём waitgroup
2. лочим мьютекс
3. создаём 1000 рутин в каждой из которых
3.1 лочим мьютекс
3.2 разлочивам мьютекс
3.3 делаем Done в waitgroup
4. Разлочиваем мьютекс тем самым создаём «лавину» — 1000 одновременно ожидающих мьютексов
5. ждём waitgroup он освободится после последнего анлока

Результаты:
BenchmarkN0T_RWTMutexTryLockUnlock-8 2174 535396 ns/op 36406 B/op 380 allocs/op
BenchmarkN0T_RWMutexLockUnlock-8 2400 447199 ns/op 52 B/op 1 allocs/op
BenchmarkN0T_MutexLockUnlock-8 3020 388154 ns/op 39 B/op 1 allocs/op


Мой мьютекс дольше стандартных на ~ 30 %
А а RW мьютекс дольше простого на ~ 15 %

Код бенча примера
func BenchmarkN0T_RWTMutexTryLockUnlock(b *testing.B) {
	ctx := context.Background()
	mx := RWTMutex{}

	k := 1000

	for i := 0; i < b.N; i++ {
		var wg sync.WaitGroup
		wg.Add(k)

		mx.TryLock(ctx)
		for j := 0; j < k; j++ {
			go func() {
				mx.TryLock(ctx)

				mx.Unlock()
				wg.Done()
			}()
		}
		mx.Unlock()

		wg.Wait()
	}
}
Про 100 потоков — организовать сложно, но вот с 2мя тест есть, как раз его написал по исходному замечанию. И он показывает, что обычные мьютексы быстрее на четверть, чем мой. + мой жрёт доп память на каждую итерацию примерно 200 байт.
Да увы, я как почитал конд тоже думал получится приспособить, но нет путей. Жалко.
Бенчмарки 2х видов

Для незаблокированного мьютекса, тут просто заблокировать и разблокировать когда нет параллельного потока, то есть случай, когда по коду пойдёт быстрый случай:
func BenchmarkRWTMutexTryLockUnlock(b *testing.B) {
	ctx := context.Background()
	mx := RWTMutex{}

	for i := 0; i < b.N; i++ {
		mx.TryLock(ctx)
		mx.Unlock()
	}
}


Для заблокированного мьютекса, тут требуется организовать 2 потока и сделать примерно следующее: блокируем мьютекс, затем создаём рутину (~ 250 нс на моей машине) в которой мьютекс разблокируем, и снова блокируем мьютекс (тут мы начинаем ожидать той самой разблокировки в параллельном потоке) и потом разблокируем мьютекс для следующей итерации.
Получается:
func BenchmarkDT_RWTMutexTryLockUnlock(b *testing.B) {
	ctx := context.Background()
	mx := RWTMutex{}

	for i := 0; i < b.N; i++ {
		mx.TryLock(ctx)

		go func() {
			mx.Unlock()
		}()

		mx.TryLock(ctx)
		mx.Unlock()
	}
}


По поводу sync.Cond. Я потоки лочу через select { case } это позволяет мне их разблокировать несколькими независимыми событиями. Если я буду блочить через Cond.Wait() то мне придётся разлочивать Cond на основании <-ctx.Done(), а этого я без дополнительного потока не смогу сделать.

Как-то так.
Добавил бенчмарки:
BenchmarkDT_RWTMutexTryLockUnlock-8 1911730 625 ns/op 192 B/op 1 allocs/op
BenchmarkDT_RWMutexLockUnlock-8 2423566 494 ns/op 0 B/op 0 allocs/op
BenchmarkDT_MutexLockUnlock-8 2577855 466 ns/op 0 B/op 0 allocs/op


Потребляется память за счёт того, что пересоздаётся канал.

Оценил Cond. Реализацию с ним плохо представляю без создания доп потока (а это 4 кб) так, что бы корректно реагировать на ctx.Done().

Если я не прав прошу меня попправить.
Спасибо, что прочитали и вникли в тему.

По поводу cond, я как то упустил из виду. Бегло прочитав я согласен, что вероятно он подходит больше. Я думаю я перепишу с его использованием или использованием того, что под ним лежит. Но пока по изучаю.

Да бенчи проверяют только быстрый путь. Дополню вместе с переходом на cond.

И видимо нужно добавить еще пару веселых фишек типа поднятие уровня блокировки RLock -> Lock и понижение, и некую приоритезацию для блокировки. Они не дороги в разработке и по идее по ресурсам тоже.

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Зарегистрирован
Активность