Tsan более тупой, но он не дает false positive. Он может пропустить ошибки, это да.
Если билд систему сложно менять, то можно сделать «compiler script»:
$ cat /xxx/g++
#!/usr/bin/env bash
clang++ -fsanitize=thread $@
$ export PATH=/xxx:$PATH
> Пауза зависит не только объема живых данных, но и от объема мертвых данных, не так ли?
Не так.
Смотрите ссылку выше — там все описано.
> GC должен бы запускаться более-менее равномерно при более-менее равномерной работе, а этого не происходит
Это действительно должно происходить. Не могу ничего сказать, т.к. у меня нет никаких данных.
> Согласитесь — куда предпочтительней иметь настройку максимального времени паузы
Не соглашусь.
Для non-concurrent mark&sweep GC такую гарантию нельзя обеспечить в общем случае. А там где можно, ее обеспечение может привести либо к чрезмерно большому потреблению памяти, либо к чрезмерно большому оверхеду на сборку мусора.
> Пулы ресурсов хорошо ложатся на архитектуру, но они помогут от GC, только если создание нового объекта требует много аллокаций/деаллокаций делать.
Пулы ресурсов помогают уменьшить частоту сборок мусора независимо от количества аллокаций при создании объекта. Однако пулы не уменьшают время сборки.
В Go сборка мусора запускается, когда объем мусора в куче равен объему живых объектов. Если переиспользуются старые объекты, то количество мусора не растет.
В Go 1.3 для пуллинга ресурсов появился специальный компонент sync.Pool.
> А есть гарантии, что уменьшение GOGC уменьшит максимальное время работы GC и разброс длительности паузы? Из документации мне это не очевидно. Но я протестирую, просто пока не было возможности.
> На самом деле есть вариант без дедлока с апгрейдом через блокирующийся TryLock()
Не вижу, как это решает проблему изменившихся данных. И как будет выглядеть код? Если TryUpgrade провалился, то что? Мы все равно делаем RUnlock и Lock? Выглядит бессмысленно
> Пардон, но работать как-то нужно
Не понимаю связи. Работать — надо. Вызывать runtime.GC не надо. Вместо этого нужно уменьшить GOGC, что приведет к увеличению частоты сборок.
> Вычисления моего кода просто теряются на фоне задач, которыми приходится заниматься runtime
Это вполне может быть из-за неоптимальности кода программы. Например, cgocall/System занимает много времени, тк вы делаете 6 cgo вызовов вместо 1 или 2.
> Ещё больше расстраивает невозможность проапгрейдить блокировку RWMutex — если блокировка в статусе RLock и обнаружилось, что необходимо внести изменения — извольте делать RUnlock(), затем Lock() и ещё раз проверять, есть ли все ещё неоходимость делать эти изменения или какая-то горутина уже всё успела сделать.
Это принципиально нельзя сделать. Если 2 горутины захватили RLock, и пытаются проапгрейдить его до Lock, они дедлочатся. Все, что тут можно сделать, это собственно и есть RUnlock и Lock. И, да, данные могут поменяться, но от этого никуда не деться.
> Также нет неблокирующей функции TryLock()
Если нужно делать только TryLock, то это легко решается с помощью atomic.Swap(&x, 0, 1). Если нужно делать и TryLock и Lock, то, да, это считается слишком сложным для мьютексов,
> Решение — запускать GC() почаще, для надежности лучше самостоятельно из программы.
Этого ни в коем случае не надо делать. Если нужно, что бы GC запускался чаще, то нужно ставить переменную окружения GOGC в значение меньше 100. Хотя в 1.3 на время паузы это не должно особо влиять (тк sweep фаза происходит одновременнно с работой приложения), только уменьшать объем памяти
Если билд систему сложно менять, то можно сделать «compiler script»:
$ cat /xxx/g++
#!/usr/bin/env bash
clang++ -fsanitize=thread $@
$ export PATH=/xxx:$PATH
Не так.
Смотрите ссылку выше — там все описано.
> GC должен бы запускаться более-менее равномерно при более-менее равномерной работе, а этого не происходит
Это действительно должно происходить. Не могу ничего сказать, т.к. у меня нет никаких данных.
> Согласитесь — куда предпочтительней иметь настройку максимального времени паузы
Не соглашусь.
Для non-concurrent mark&sweep GC такую гарантию нельзя обеспечить в общем случае. А там где можно, ее обеспечение может привести либо к чрезмерно большому потреблению памяти, либо к чрезмерно большому оверхеду на сборку мусора.
Пулы ресурсов помогают уменьшить частоту сборок мусора независимо от количества аллокаций при создании объекта. Однако пулы не уменьшают время сборки.
В Go сборка мусора запускается, когда объем мусора в куче равен объему живых объектов. Если переиспользуются старые объекты, то количество мусора не растет.
В Go 1.3 для пуллинга ресурсов появился специальный компонент sync.Pool.
Нет, точно так же как и для ручного вызова runtime.GC.
В 1.3 обе эти вещи не должны особо влиять на время паузы. Пауза в основном зависит от объема живых данных в программе. Вот тут я описывал это более подробно:
software.intel.com/en-us/blogs/2014/05/10/debugging-performance-issues-in-go-programs
От этого никуда не деться, но это не значит, что их нужно делать в неимоверных количествах. Вызовы можно агрегировать прямо в Go файле как:
/*
#include «somelib.h»
void myWrapper(...) {
… malloc(...);
someLibCall1(...);
someLibCall2(...);
free(...)
}
*/
import «C»
C.myWrapper(...)
> На самом деле есть вариант без дедлока с апгрейдом через блокирующийся TryLock()
Не вижу, как это решает проблему изменившихся данных. И как будет выглядеть код? Если TryUpgrade провалился, то что? Мы все равно делаем RUnlock и Lock? Выглядит бессмысленно
> Пардон, но работать как-то нужно
Не понимаю связи. Работать — надо. Вызывать runtime.GC не надо. Вместо этого нужно уменьшить GOGC, что приведет к увеличению частоты сборок.
Это вполне может быть из-за неоптимальности кода программы. Например, cgocall/System занимает много времени, тк вы делаете 6 cgo вызовов вместо 1 или 2.
> Ещё больше расстраивает невозможность проапгрейдить блокировку RWMutex — если блокировка в статусе RLock и обнаружилось, что необходимо внести изменения — извольте делать RUnlock(), затем Lock() и ещё раз проверять, есть ли все ещё неоходимость делать эти изменения или какая-то горутина уже всё успела сделать.
Это принципиально нельзя сделать. Если 2 горутины захватили RLock, и пытаются проапгрейдить его до Lock, они дедлочатся. Все, что тут можно сделать, это собственно и есть RUnlock и Lock. И, да, данные могут поменяться, но от этого никуда не деться.
> Также нет неблокирующей функции TryLock()
Если нужно делать только TryLock, то это легко решается с помощью atomic.Swap(&x, 0, 1). Если нужно делать и TryLock и Lock, то, да, это считается слишком сложным для мьютексов,
> Решение — запускать GC() почаще, для надежности лучше самостоятельно из программы.
Этого ни в коем случае не надо делать. Если нужно, что бы GC запускался чаще, то нужно ставить переменную окружения GOGC в значение меньше 100. Хотя в 1.3 на время паузы это не должно особо влиять (тк sweep фаза происходит одновременнно с работой приложения), только уменьшать объем памяти