unordered_map выбрасывает bad_alloc в VS10, но не в VS9, это ошибка? - PullRequest
4 голосов
/ 07 июля 2010

Пока пишу пост о проекте 14-й проблемы Эйлера Я столкнулся с разницей в поведении между VC9 и VC10.

Следующий код работает нормально в VC9, нов VC10 std::unordered_map выдает bad_alloc исключение.Странно то, что если я восстановлюсь после исключения, будущие выделения будут выполнены успешно (размер контейнера продолжает расти).Также, если я использую boost::unordered_map, он отлично работает в обоих компиляторах.

Что касается фактического использования памяти, я работаю на машине с 4 ГБ ОЗУ (используется 1,7), версия VC9 получает до ~ 810 МБпамяти до завершения задачи, и VC10 один падает примерно на 658 МБ.

Это ошибка в VC10?Я работаю на той же машине, что еще может привести к постоянному исчерпанию памяти в одной версии, а не в другой, когда объем выполненной работы идентичен?


Дополнительная информация: Первое исключение происходит при вычислении 7 718 688 с глубиной стека 1 (без рекурсии, только main-> length).После этого, похоже, это происходит для каждого числа, которое добавляется в кеш.Кэш содержал 16 777 217 элементов до возникновения исключения (согласно cache.size()).Интересно то, что даже когда insert терпит неудачу, размер кэша увеличивается на единицу, поэтому кажется, что он не обеспечивает строгой гарантии исключения (в нарушение §23.2.1.11).

Код следует:

#include <iostream>
#include <unordered_map>

typedef std::unordered_map<_int64, int> cache_type;

_int64 collatz(_int64 i)
{
    return (i&1)? i*3+1 : i/2;
}

int length(_int64 n, cache_type& cache)
{
    if (n == 1)
        return 1;

    cache_type::iterator found = cache.find(n);
    if (found != cache.end())
        return found->second;
    int len = length(collatz(n), cache) + 1; 
    cache.insert(std::make_pair(n, len)); // this sometimes throws
    return len;
}

int main(int argc, char** argv)
{
    const int limit = 10000000;
    cache_type cache;
    std::pair<int, int> max = std::make_pair(0, 0);
    for (int i = 2; i <= limit; ++i) {
        int len = length(i, cache);
        if (len > max.second)
            max = std::make_pair(i, len);
    }

    std::cout<< "Number with longest orbit is " << max.first 
        << " with a lenght of " << max.second 
        << " cache size is " << cache.size() << std::endl;
}


Также любой может воспроизвести это поведение, когда-то оно исчезло (и вновь появилось)поэтому в моей конфигурации может быть что-то особенное.

Ответы [ 5 ]

2 голосов
/ 16 июля 2010

Это может быть случайным, но изменение значения _SECURE_SCL вызывает поведение, которое вы описываете.

Т.е. компиляция с:

cl /EHa /MD /D_SECURE_SCL=1 /Ox /c t1.cpp
link /LIBPATH:"c:/Program Files/Microsoft Visual Studio 10.0/VC/lib" /LIBPATH:"C:/Program Files/Microsoft SDKs/Windows/v7.0A/Lib" t1.obj

вылетает, но те же команды с _SECURE_SCL = 0 выполняются до конца на моей 32-битной машине XP. Страница msdn для _SECURE_SCL говорит, что она включена для отладочных сборок, но не для выпуска, что может быть важно, если вы собираете под IDE.

1 голос
/ 11 июля 2010

Вставка одного элемента может привести к большому выделению памяти, если необходимо изменить размер хеш-таблицы карты. Карта кажется около 0,5 ГБ в конце пробега. (См. Мой комментарий выше.)

Предположительно, существует некоторая эвристика, используемая для решения, насколько увеличить хеш-таблицу, когда она должна расти, и это, возможно, может быть удвоение ее каждый раз. Поэтому при копировании хеш-таблицы будет использоваться ~ 1,5 ГБ для старых + новых данных.

Следовательно, возможно, ваша программа превысила объем памяти процесса. (См. Комментарий еще раз.) Если это так, то возможно, что VC10 в целом занимает немного больше памяти, чем VC9, и что несколько разные объемы памяти выделяются при разных запусках или сборках программы, так что VC10 иногда достигает предела, а VC9 не ' когда-нибудь ударил его.

0 голосов
/ 14 июля 2010

1 - Проверьте EventLog, чтобы увидеть, есть ли какие-либо события, говорящие о процессе, проходящем через разрешенную квоту.

2 - Если вы работаете в 32-битной ОС, попробуйте запустить его с 3 ГБ для пространства пользователя.

3 - посмотрите, есть ли у вас разные распределители

4 - Отклонить unordered_map в 9.0 и 10.0 и включить его в файл на случай, если будет добавлен искусственный ограничитель размера («функции безопасности» :-). Скорее всего, это будет макрос с разными значениями для сборки x86 и x64.

5 - Постарайтесь поместить легкую обертку вокруг распределителя и просто печатать размеры для каждого выделения. Это также скажет вам, если это действительно распределитель, который бросает или что-то перед ним.

6 - Если это распределитель, бросающий взгляд на фактические вызовы WinNT API, сделанные из него (и снова diff с 9.0)

7 - Попробуйте предварительно выделить огромный блок (скажем, 1 ГБ).

0 голосов
/ 07 июля 2010

Вы разыгрываете стек в глубоко рекурсивном вызове length().

0 голосов
/ 07 июля 2010

Есть ли у _int64 требования к выравниванию, которые карта может не учитывать при выделении?

Попробуйте вместо этого использовать long long int и посмотрите, не изменится ли поведение.

...