Улучшение производительности кучи памяти в C ++ - PullRequest
0 голосов
/ 16 апреля 2011

Я пишу функцию, где мне нужно значительное количество кучи памяти. Можно ли сказать компилятору, что эти данные будут часто использоваться в определенном цикле for, чтобы повысить производительность (с помощью параметров компиляции или подобного)?

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

Сейчас код работает, но я думаю, что он может быть быстрее.

UPDATE: Я делаю что-то вроде этого

vector< set<uint> > vec(node_vec.size());
for(uint i = 0; i < node_vec.size(); i++)
  for(uint j = i+1; j < node_vec.size(); j++)
    // some computation, basic math, store the result in variable x
      if( x > threshold ) {
         vec[i].insert(j);
         vec[j].insert(i);
      }

некоторые детали:
- Я использовал hash_set, небольшое улучшение, кроме того факта, что hash_set доступен не на всех машинах, которые у меня есть для целей моделирования
- Я попытался выделить vec в стеке, используя массивы, но, как я уже сказал, я могу получить ошибку сегментации, если число элементов слишком велико

Если node_vec.size (), скажем, равен k, где k порядка нескольких тысяч, я ожидаю, что vec будет в 4 или 5 раз больше, чем node_vec. При таком порядке величины код выглядит медленным, учитывая тот факт, что мне приходится запускать его много раз. Конечно, я использую многопоточность для распараллеливания этих вызовов, но я не могу заставить функцию работать как можно быстрее, чем то, что я вижу сейчас.

Можно ли, например, выделить vec в кэш-памяти для быстрого извлечения данных или что-то подобное?

Ответы [ 5 ]

3 голосов
/ 16 апреля 2011

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

Это не то, что вы действительно можете оптимизировать на уровне компилятора. Я думаю, что вы беспокоитесь о том, что у вас много памяти, которая может быть «устаревшей» (выгруженной), но в определенный момент времени вам придется перебирать все это, возможно, несколько раз, и вам не нужна память страницы, которые будут выгружены на диск.

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

Возможно, вы также захотите изучить кучу с низкой фрагментацией (однако это может вообще не относиться к этой проблеме) и эту страницу , которая описывает строки кэша относительно for петли.

Последняя страница о мельчайших подробностях работы ЦП (деталь, с которой вам обычно не нужно беспокоиться) в отношении доступа к памяти.

Пример 1. Доступ к памяти и производительность Насколько быстрее вы ожидаете запустить цикл 2 по сравнению с циклом 1?

int[] arr = new int[64 * 1024 * 1024];

// Loop 1
for (int i = 0; i < arr.Length; i++) arr[i] *= 3;

// Loop 2
for (int i = 0; i < arr.Length; i += 16) arr[i] *= 3;

Первый цикл умножает каждое значение в массиве на 3, а второй цикл умножает только каждый 16-й. Второй цикл выполняет только около 6% работы первого цикла , но на современных машинах два цикла for занимают примерно одинаковое время : 80 и 78 мс соответственно на моя машина.

1 голос
/ 16 апреля 2011

UPDATE

vector< set<uint> > vec(node_vec.size());
for(uint i = 0; i < node_vec.size(); i++)
  for(uint j = i+1; j < node_vec.size(); j++)
    // some computation, basic math, store the result in variable x
      if( x > threshold ) {
         vec[i].insert(j);
         vec[j].insert(i);
      }

Это все еще мало что говорит, потому что мы не можем знать, как часто условие x > threshold будет выполнено. Если x > threshold очень часто верно, то узким местом может быть std::set, поскольку он должен динамически распределять память для каждого uint, который вы вставляете.

Также мы не знаем, что на самом деле означает «какое-то вычисление». Если это много или неправильно, это может стать узким местом.

И мы не знаем, как вам нужно получить доступ к результату.

Во всяком случае, на догадку:

    vector<pair<int, int> > vec1;
    vector<pair<int, int> > vec2;

    for (uint i = 0; i < node_vec.size(); i++)
    {
        for (uint j = i+1; j < node_vec.size(); j++)
        {
            // some computation, basic math, store the result in variable x
            if (x > threshold)
            {
                vec1.push_back(make_pair(i, j));
                vec2.push_back(make_pair(j, i));
            }
        }
    }

Если вы можете использовать результат в этой форме, все готово. В противном случае вы могли бы сделать некоторую постобработку. Только не копируйте это в std::set снова (очевидно). Попробуйте придерживаться std::vector<POD>. Например. Вы можете построить индекс для векторов следующим образом:

    // ...
    vector<int> index1 = build_index(node_vec.size(), vec1);
    vector<int> index2 = build_index(node_vec.size(), vec2);
    // ...
}    

vector<int> build_index(size_t count, vector<pair<int, int> > const& vec)
{
    vector<int> index(count, -1);

    size_t i = vec.size();
    do
    {
        i--;
        assert(vec[i].first >= 0);
        assert(vec[i].first < count);
        index[vec[i].first] = i;
    }
    while (i != 0);

    return index;
}

пс .: Я почти уверен, что ваш цикл не ограничен памятью. Хотя не могу быть уверен ... если "узлы", которые вы нам не показываете, действительно большие, это все равно может быть.


Оригинальный ответ:

Нет простого I_will_access_this_frequently_so_make_it_fast(void* ptr, size_t len) -видного решения.

Вы можете сделать кое-что, хотя.

  1. Убедитесь, что компилятор может «видеть» реализацию каждой функции, вызываемой внутри критических циклов. Что необходимо для того, чтобы компилятор мог «видеть» реализацию, зависит от компилятора. Однако есть один способ убедиться в этом: определить все соответствующие функции в одной и той же единице перевода перед циклом и объявить их как inline.

    Это также означает, что вы должны не каким-либо образом вызывать "внешние" функции в этих критических циклах. Под «внешними» функциями я подразумеваю такие вещи, как системные вызовы, материал библиотеки времени выполнения или материал, реализованный в DLL / SO. Также не вызывайте виртуальные функции и не используйте указатели на функции. И, конечно же, не выделяйте и не освобождайте память (внутри критических циклов).

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

  3. Используйте наименьшие возможные типы. Например. не используйте int, если signed char сделает работу. Это то, что я обычно не рекомендую, но при обработке большого объема памяти это может значительно увеличить производительность. Особенно в очень плотных петлях.

  4. Если вы просто копируете или заполняете память, используйте memcpy или memset. Отключить внутреннюю версию этих двух функций, если порции больше, чем примерно от 50 до 100 байтов.

  5. Убедитесь, что вы получаете доступ к данным в режиме кэширования. Оптимальным является «потоковая передача» - то есть доступ к памяти с восходящими или нисходящими адресами. Вы можете «прыгать» вперед на несколько байтов за раз, но не прыгайте слишком далеко. Худшее - это произвольный доступ к большому блоку памяти. Например. если вам нужно работать с двумерной матрицей (например, растровым изображением), где p [0] - p [1] - это шаг «вправо» (x + 1), убедитесь, что внутренний цикл увеличивает x, а внешний увеличивает у. Если вы сделаете это с другой стороны, производительность будет намного хуже.

  6. Если ваши указатели не содержат псевдонимов, вы можете указать компилятору (как это будет зависеть от компилятора). Если вы не знаете, что означает отсутствие псевдонима, я рекомендую поискать в сети и документации вашего компилятора, так как объяснение выходит за рамки.

  7. При необходимости используйте внутренние инструкции SIMD.

  8. Используйте явные инструкции предварительной выборки, если вы знаете, какие ячейки памяти понадобятся в ближайшем будущем.

0 голосов
/ 16 апреля 2011

Предполагая, что вы выделяете данные из кучи только один раз перед выполнением цикла, обратите внимание, что @lvella означает, что память является памятью, и если к ней часто обращаются, она должна эффективно кэшироваться во время выполнения.

0 голосов
/ 16 апреля 2011

Компилятор уже может видеть, что к данным часто обращаются в цикле.

0 голосов
/ 16 апреля 2011

Вы не можете сделать это с опциями компилятора.В зависимости от вашего использования (вставка, произвольный доступ, удаление, сортировка и т. Д.), Вы можете получить более подходящий контейнер.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...