Большие массивы, std::vector и переполнение стека

У меня есть программа, которая считывает данные из больших массивов, я сначала разделил программу на два отдельных проекта в Visual Studio, и каждый из них работал отлично, но когда я попытался собрать их, программа пошла смешно, пропуская несколько шагов, отладки. Я очень новичок в С++, поэтому я начал делать некоторые исследования, и я обнаружил, что, возможно, я заполнял стек этими огромными массивами и что я должен попытаться поместить их в кучу.

Я решил изменить каждый из массивов на std::vector и инициализировать их следующим образом:

std::vector<double> meanTimeAO = { 0.4437, 0.441, 0.44206, 0.44632, 0.4508, 0.45425,...}

Но после изменения всех массивов сейчас, когда я пытаюсь скомпилировать компилятор с переполнением стека, я думал, что освобождаю пространство памяти из стека, меняя массивы как вектор, но, похоже, у меня были противоположные результаты, почему это?

и как я должен работать с этими большими массивами? (они фиксируются, никогда не изменяя значения или размер)

Ответ 1

Если массив имеет фиксированный размер и его элементы не изменяются, нет необходимости использовать vector. Вместо этого вы можете использовать std::array, массив const или constexpr.

constexpr float meanTimeAO[] = { 0.4437f, 0.441f, 0.44206f, 0.44632f, 0.4508f, 0.45425f,...}

Ответ 2

Как ответ @Ajay и комментарий @Cornstalks правильно указывают, что вы можете полностью избежать стека и кучи, используя квалификатор static или constexpr на вашем массиве

const static std::array<float, 1000000> a1 = {}; // OK
constexpr    std::array<float, 1000000> a2 = {}; // OK in C++11 onwards

Здесь хранится массив в разделе инициализации данных вашей памяти (хорошее объяснение здесь). const служит только для запрета модификации a1 и не требуется для предотвращения. Переменные, объявленные как constexpr, также автоматически const и, следовательно, не нуждаются в определителе.

Примечание. Вы также можете добиться эффектов static, создав глобальные переменные массива, хотя я бы рекомендовал не.

Переполнение стека программ

Если ваши данные нестатические, вы должны использовать std::vector (или другие типы памяти, выделенной кучей), когда количество элементов очень велико.

std::array<float, 1000000> a = {};   // Causes stack-overflow on 32-bit MSVS 2015
std::vector<float> v(1000000);       // OK

Это связано с тем, что размер стека по умолчанию составляет ~ 1 МБ, а 1 млн. поплавков - ~ 4 МБ. Размер кучи ограничен вашей доступной оперативной памятью (ОЗУ). Подробнее о стеке и куче здесь.

Недостатки std::vector заключаются в том, что он немного медленнее, чем std::array (выделение памяти, освобождение памяти и доступ все медленнее, чем у стека) и что это не фиксированный размер. Однако вы можете объявить свой std::vector как const, чтобы он (или кто-то еще) не мог случайно изменить его размер или элементы.

const std::vector<float> v = {...}; 

Теперь, почему ваш std::vector вызывает переполнение стека, это немного загадка. Однако, в то время как std::vector выделяет свои элементы в куче, он также выделяет в стек указатель (4 байта на 32-битных и 8-байтовых в 64-битных). Таким образом, если у вас есть более ~ 250 000 std::vector всех в области одновременно, это также вызовет переполнение стека (или ~ 125 000 в 64-разрядных системах).

Переполнение стека компилятора

Компилятор, как и любая программа, выделяет память - некоторые из которых будут в стеке. Официальная ошибка для компилятора на MSVC Fatal Error C1063.

Учитывая, что ваш отладчик ведет себя странно, мой совет состоял в том, чтобы попытаться изолировать проблемный код, вручную разбив код на модульные единицы и составляя их индивидуально. Возможно, что небольшая часть кода может нести ответственность за ошибку, съедая много стека, например. путем рекурсивного генерации большого числа функций.

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

Улучшение кода

Чтобы улучшить код, вы можете попытаться разбить ваши данные на куски. Например, вы можете: читать в ~ 256 КБ стоимости массива, обрабатывать его, записывать массив обратно в файл, а затем переходить к следующему 256 КБ. Вы также можете выбрать размер куска меньше размера вашего кеша L1 (чтобы он мог быть сохранен сразу), что улучшило бы производительность, минимизируя промахи в кэше.

Примечания

  • MSVS 2015 (обновление 2) создает внутреннюю ошибку компилятора при компиляции

    #include "stdafx.h"
    #include <array>
    int main()
    {
         constexpr std::array<int, 1000000> a = {};
         return 0;
    }
    

    Вариант static const отлично работает, и если я перемещаю a за пределы main (делая его глобальной переменной), то он также отлично работает.

  • Не наличие chkstk.asm необычно. Шахта находится по адресу: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\i386\chkstk.asm. Если вам это не хватает, возможно, попробуйте переустановить MS Visual Studio.

Ответ 3

Указывая, может быть, в приведенных выше ответах есть несколько вещей:

  • "при выполнении отладки было смешно пропустить некоторые шаги". OP, можете ли вы предоставить более подробную информацию о том, что вы имеете в виду? Возможно, вы отлаживали сборку релизов, и, когда вы просматриваете код, вы замечаете, что строка выполняемого кода не та, которую вы ожидаете выполнить дальше (что совершенно правильно для сборки релиза с оптимизацией компилятора).

  • Вопрос о том, что компилятор разбился с переполнением стека. Не выполненная программа. Таким образом, проблема связана с проблемой компилятора. Конечно, изменение кода может привести к сбою компилятора, но комментарий выше о распределении std::vector в стеке не связан с тем, что может вызвать сбой компилятора.

Предложения: вы могли бы попытаться увидеть, есть ли какие-либо известные ошибки в используемой вами версии компилятора (например, посмотрите, выпустил ли ваш компилятор более новую версию, которая может привести к сбою компилятора).

Кроме того, в частности, для получения комментария "они никогда не меняются по значению или размеру", попробуйте поместить свои данные в двойные массивы static const, а не std::vectors (или даже связанные списки). Неизменяемый статически распределенный список, связанный с чтением, является пустой тратой времени, когда вам, вероятно, следует просто использовать double []. статические константные данные инициализируются во время компиляции/связи, а не во время выполнения, и существуют в своей собственной области памяти (строго говоря, ни стека, ни кучи).