Столкнулся недавно с интересной проблемой, которая себя очень странно проявляла, но в конце концов оказалась следствием ошибки алгоритмического характера.
Интересно было то, что как только обнаружилась причина проблемы, как все ее косвенные проявления сразу встали на свои места, а их объяснение стало абсолютно логичным.
Может быть будет интересно кому еще, поэтому выкладываю здесь.
Есть класс (C++), представляющий из себя коллекцию некоторых записей. Назовем его Rowset. Работал он долго, без каких-либо видимых проблем. Но в один прекрасный день кто-то заметил, что на платформе S этот Rowset отнимает необычно много памяти, когда количество записей достигает где-то сто тысяч. На других платформах, L (A и H), такого эффекта не наблюдалось. Стали экспериментировать, оказалось что если добавлять в Rowset записи по одной и их количество довести до пол-милиона/миллион, то на платформе S наш процесс с Rowset вылетает с ошибкой out-of-memory, достигая в памяти размера 4GB. На платформах L (A,H) вроде все с памятью нормально. Но только там при большом количестве записей, Rowset начинает как-то подозрительно медленно работать. Т.е. десятки тысяч записей добавляются за секунду-две, а сотни тысяч уже сотню секунд, несколько минут (при этом отнимая в памяти где-то около 100MB, т.е. не так много). Наоборот, на платформе S, какое-то непропорциональное разбухание в памяти, т.е. до определенного количество записей размер выделяемой памяти не сильно отличается от того, что наблюдается на платформах L (A,H), а потом он резко растет.
Поискали утечки памяти. Вроде все нормально, утечек нет. Да и потом, как так может быть, что утечки есть только на одной платформе, а на других этот же код выполняется без утечек?
Внимание, вопрос: в чем может быть проблема и как ее искать?
Здравствуйте, Сергей Мухин, Вы писали:
СМ>Здравствуйте, alexeiz, Вы писали:
A>>Внимание, вопрос: в чем может быть проблема и как ее искать?
СМ>такое поведение может быть при неверной hash функции
Здравствуйте, alexeiz, Вы писали:
СМ>>такое поведение может быть при неверной hash функции
A>Каким образом?
если она всегда выдает одно значение (а это легко может быть платформозависимо), то вместо равномерного распределения, получите одну обыкновенную цепочку, с соответсвущ потерей памяти (правда не значительно) и большой потерей производительности.
Здравствуйте, alexeiz, Вы писали:
A>Может быть будет интересно кому еще, поэтому выкладываю здесь.
Данетка?
A>Есть класс (C++), представляющий из себя коллекцию некоторых записей.
А какой контейнер в коллекции?
Так навскидку похоже на поведение хэш-таблицы — в одном случае (когда кончается память) происходит слишком широкое перераспределение, а в другом (с тормозами) его не происходит, но в корзинке образуется много коллизий и поиск внутри корзинки начинает тормозить. По идее, это и является критерием для перераспределения, непонятно, почему его не происходит, особенно если хэш-функция одна и та же и реализация таблицы (т.е. параметры, управляющие перераспределением) тоже одна и та же. Но если и то, и другое разное на разных платформах, это может быть причиной.
Здравствуйте, jazzer, Вы писали:
J>Так навскидку похоже на поведение хэш-таблицы — в одном случае (когда кончается память) происходит слишком широкое перераспределение, а в другом (с тормозами) его не происходит, но в корзинке образуется много коллизий и поиск внутри корзинки начинает тормозить. По идее, это и является критерием для перераспределения, непонятно, почему его не происходит, особенно если хэш-функция одна и та же и реализация таблицы (т.е. параметры, управляющие перераспределением) тоже одна и та же. Но если и то, и другое разное на разных платформах, это может быть причиной.
да, про ошибку в перераспределение я и забыл. но ее вероятность меньше, а так же сложней сделать эту часть платформозависимой. а хэш ф-ия легко — например используются адреса объектов, а они могут иметь какие-то быить всегда 0 (или 1) на конкр платфо.
Здравствуйте, alexeiz, Вы писали:
A>Поискали утечки памяти. Вроде все нормально, утечек нет. Да и потом, как так может быть, что утечки есть только на одной платформе, а на других этот же код выполняется без утечек?
A>Внимание, вопрос: в чем может быть проблема и как ее искать?
Как недавно были убедился, но отсутствие утечек памяти не говорит, что с памятью всё в порядке. Была ситуация, когда всё работает отлично, НО при завершении работы, когда идёт всеобщая чистка, примерно в одном случае из пяти вываливалось предупреждение о записи в уже освобождённую ранее память кучи, при этом стек мог указывать на любое место от деструкторов std коллекций до вызова функции с cdcdcdcd данными. Для повышения вероятности появления ошибки поток данных надо было довести до нескольких сотен тысяч записей. Так как это была не утечка, которую можно поймать простым макросом по перегрузке new, то после безуспешных поисков в Интернете просто переписал new и delete на свои функции, которые при инициализации просто выделяют огромный кусок памяти (простой массив), а при каждом обращении берут очередной элемент и отдают его, протоколируя в какой момент какие участки памяти были выделены и освобождены (повторного использования памяти не было). С первого же раза отладчик поймал нужную функцию. Причина оказалась в том, что у меня были два класса А и Б, при этом для А выделялись куски при первичном обращении, а Б это постоянные new и delete. Думал, и всё время ошибки оказывались рядом с классом Б, поэтому его new и delete и перегрузил, а проблема была в классе А, где я два раза умудрялся вызвать delete.
Поэтому если проблема с памятью, то надо постараться просто отследить какие куски кода выделяются/освобождаются и вывести в лог файл, кто запросил и почему. В любом случае, ошибка должна быть в программе, хотя постоянно и хочется верить в чудеса.
и еще. На одной и windows системе (давно было наверно это был windowsNT4, а мб еще раньше) было замечено очень плохое поведение кучи, когда число аллокаций приближается к миллиону. Очень медленно работало. Пришлось написать свою кучу. Но в след windows версии — все работало быстро.
Здравствуйте, alexeiz, Вы писали:
A>Внимание, вопрос: в чем может быть проблема и как ее искать?
А не используется ли каких временных сущностей? Например, преобразование в DOM-модель и обратно, с целью верификации? Или ведение лога обработанных сущностей? Или какой-то статистической таблицы — тоже можно весьма шустро (и нелинейно) нарастить аппетиты по памяти с ростом объема данных.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, alexeiz, Вы писали:
A>>Может быть будет интересно кому еще, поэтому выкладываю здесь. J>Данетка?
A>>Есть класс (C++), представляющий из себя коллекцию некоторых записей. J>А какой контейнер в коллекции? J>Так навскидку похоже на поведение хэш-таблицы — в одном случае (когда кончается память) происходит слишком широкое перераспределение, а в другом (с тормозами) его не происходит, но в корзинке образуется много коллизий и поиск внутри корзинки начинает тормозить. По идее, это и является критерием для перераспределения, непонятно, почему его не происходит, особенно если хэш-функция одна и та же и реализация таблицы (т.е. параметры, управляющие перераспределением) тоже одна и та же. Но если и то, и другое разное на разных платформах, это может быть причиной.
Код выполняется абсолютно один и тот же на всех платформах. Если бы на одной было перераспределение, то и на других оно бы было тоже.
Rowset не использует хэш-таблицы. Там ничего сложнее векторов нет. Правда код там запутанный. Сразу идти и анализировать код с надеждой наткнуться на причину было бы бесполезно.
Здравствуйте, valaar, Вы писали:
V>Как недавно были убедился, но отсутствие утечек памяти не говорит, что с памятью всё в порядке. Была ситуация, когда всё работает отлично, НО при завершении работы, когда идёт всеобщая чистка, примерно в одном случае из пяти вываливалось предупреждение о записи в уже освобождённую ранее память кучи, при этом стек мог указывать на любое место от деструкторов std коллекций до вызова функции с cdcdcdcd данными. Для повышения вероятности появления ошибки поток данных надо было довести до нескольких сотен тысяч записей.
Прострелов по памяти не оказалось. На платформе S, заменили malloc/free на slab аллокатор с отладочными опциями. Ни утечек, ни прострелов. Однако, эффект out-of-memory пропал...
Здравствуйте, alexeiz, Вы писали:
A>Здравствуйте, valaar, Вы писали:
V>>Как недавно были убедился, но отсутствие утечек памяти не говорит, что с памятью всё в порядке. Была ситуация, когда всё работает отлично, НО при завершении работы, когда идёт всеобщая чистка, примерно в одном случае из пяти вываливалось предупреждение о записи в уже освобождённую ранее память кучи, при этом стек мог указывать на любое место от деструкторов std коллекций до вызова функции с cdcdcdcd данными. Для повышения вероятности появления ошибки поток данных надо было довести до нескольких сотен тысяч записей.
A>Прострелов по памяти не оказалось. На платформе S, заменили malloc/free на slab аллокатор с отладочными опциями. Ни утечек, ни прострелов. Однако, эффект out-of-memory пропал...
Фрагментацию памяти смотрели?
Только не спрашивайте как
Что за платформа хоть? если Linux то там valgrind есть.
Я бы еще с версиями STL поигрался бы. Бывают всякие эффекты.
Здравствуйте, Sheridan, Вы писали:
S>Приветствую, valaar, вы писали:
v>> , где я два раза умудрялся вызвать delete. S>Полезно всетаки после delete указатель в NULL сбрасывать.
Так делают те "админы", у кого утечки памяти и двойное удаление — постоянная головная боль, которую они пытаются уменьшить с помощью таких "полезных" приемов. Ирония в том, что после сбрасывания указателя в NULL, его повторное (и ошибочное) удаление пройдёт абсолютно незамеченным, тем самым закапывая баг в программе, который приводит к этому двойному удалению, еще глубже.
Приветствую, alexeiz, вы писали:
a> Так делают те "админы", у кого утечки памяти и двойное удаление — постоянная головная боль, которую они пытаются уменьшить с помощью таких "полезных" приемов. Ирония в том, что после сбрасывания указателя в NULL, его повторное (и ошибочное) удаление пройдёт абсолютно незамеченным, тем самым закапывая баг в программе, который приводит к этому двойному удалению, еще глубже.
Здравствуйте, alexeiz, Вы писали:
A>Код выполняется абсолютно один и тот же на всех платформах.
ну и что, это не делает его платформо независимым.
A>Rowset не использует хэш-таблицы. Там ничего сложнее векторов нет. A>Правда код там запутанный. Сразу идти и анализировать код с надеждой наткнуться на причину было бы бесполезно.
т.е. свой код мы не смотрим, а сразу просим разобраться RSDN!
при этам ведем себя как партизан. Ни как устроен Rowset, ни что за платформы...
A>>Rowset не использует хэш-таблицы. Там ничего сложнее векторов нет. A>>Правда код там запутанный. Сразу идти и анализировать код с надеждой наткнуться на причину было бы бесполезно.
СМ>т.е. свой код мы не смотрим, а сразу просим разобраться RSDN!
СМ>при этам ведем себя как партизан. Ни как устроен Rowset, ни что за платформы...
в первом сообщении автор же написал "Столкнулся недавно с интересной проблемой, которая себя очень странно проявляла, но в конце концов оказалась следствием ошибки алгоритмического характера."
то есть ошибка уже найдена и устранена, но предлагает подумать и предложить версии, что могло быть причиной
Здравствуйте, valaar, Вы писали:
V>в первом сообщении автор же написал "Столкнулся недавно с интересной проблемой, которая себя очень странно проявляла, но в конце концов оказалась следствием ошибки алгоритмического характера." V>то есть ошибка уже найдена и устранена, но предлагает подумать и предложить версии, что могло быть причиной
вы вообще понимаете, что нам НИЧЕГО не предложили. Как можно искать алгоритмическую ошибку не зная, даже примерно, алгоритма?
И как алгоритм может зависеть от платформы (хотя выбор алгоритма может)? Ошибка в реализации алгоритма.
т.е. если помочь человеку или нельзя или поздно (раз сам решил). Жаль время.
Здравствуйте, alexeiz, Вы писали:
A>Поискали утечки памяти. Вроде все нормально, утечек нет. Да и потом, как так может быть, что утечки есть только на одной платформе, а на других этот же код выполняется без утечек?
A>Внимание, вопрос: в чем может быть проблема и как ее искать?
MALLOC_OPTIONS=KK ?
Курица — это инструмент, с помощью которого одно яйцо производит другие.