Алгоритм оптимизации вложенных циклов

существует ли алгоритм для оптимизации производительности следующего?

for (i = 0; i < LIMIT; i++) {
  for (j = 0; j < LIMIT; j++) {
   // do something with i and j
  }
 }
  • и i и j начало в 0
  • обе петли концом по тому же условие
  • и i и j увеличивается с той же скоростью

можно ли это сделать в 1 цикле каким-то образом?

7 ответов


это is можно написать это с помощью одного цикла, но я настоятельно рекомендую этого не делать. Двойной цикл for-loop-это хорошо известная идиома, которую программисты умеют читать, и если вы свернете два цикла в один, вы пожертвуете читаемостью. Более того, неясно, действительно ли это заставит код работать быстрее, поскольку компилятор уже очень хорош в оптимизации циклов. Сворачивание двух петель в одну требует дополнительной математики на каждом шаге, который почти конечно, медленнее, чем две петли независимо друг от друга.

тем не менее, если вы хотите написать это как один цикл, одна идея-подумать о итерации пробел набор пар перебрать. Сейчас это выглядит так:

(0, 0)   (0, 1),   (0, 2), ...,   (0, N-1)
(1, 0)   (1, 1),   (1, 2), ...,   (1, N-1)
                ...          
(N-1, 0) (N-1, 1), (N-1, 2), ..., (N-1, N-1)

идея состоит в том, чтобы попытаться посетить все эти пары в порядке (0, 0), (0, 1), ..., (0, N-1), (1, 0), (1, 1), ..., (1, N-1), ..., (N-1, 0), (N-1, 1), ..., (N-1, N-1). Для этого обратите внимание, что каждый раз мы увеличиваем i, мы пропускаем N элементы, в то время как приращение j мы пропускаем только над одним элементом. Следовательно, итерация (i, j) из цикла будет отображаться в положение i * N + j в линеаризованном цикле заказа. Это означает, что на итерации i * N + j, мы хотим посетить (i, j). Для этого мы можем восстановить i и j из индекса, используя простую арифметику. Если k счетчик текущего цикла, мы хотим посетить

i = k / N   (integer division)
j = k % N

таким образом, цикл может быть записан как

for (int k = 0; k < N * N; ++k) {
    int i = k / N;
    int j = k % N;
}

однако, вы должны быть осторожны с этим, потому что N * N может не вписываться в целое число и, следовательно, может переполняться. В этом случае вы хотели бы вернуться к двойному for-loop. Кроме того, введение дополнительных делений и модулей сделает этот код (потенциально) намного медленнее, чем двойной цикл for. Наконец, этот код гораздо сложнее прочитать, чем исходный код, и вам нужно будет обязательно предоставить агрессивные комментарии, описывающие, что вы здесь делаете. Опять же, я настоятельно рекомендую вам не делать этого вообще, если у вас есть очень веские основания подозревать, что существует проблема со стандартным двойным for-loop.

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

надеюсь, что это помогает!


нет способа значительно оптимизировать сам цикл. Однако, когда вы рассматриваете детали "сделать что-то с i и j", это может иметь большое значение, является ли i или j внешним циклом. Например, один порядок может вызвать много скачков в памяти или на диске, в то время как другой порядок приводит к последовательному доступу или почти так.

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


Это отличный способ понять математику обнаружения шаблона во вложенном цикле for. Эта математика полезна при применении алгоритма Aho-Corasick для сопоставления шаблонов подстрок. Я работаю над решением прямо сейчас, и я считаю, что могу просто вычислить необходимую информацию в массивно вложенном цикле for, который я реализовал. На самом деле, это действительно интересная проблема. Я отправлю код, потому что если вы посмотрите на уравнение лямбды, вы действительно вероятно, речь идет о o (n^3) по сложности для этих вложенных циклов. С ними трудно разобраться.

вот код, который я бегал вокруг:

output_timer("Beginning the search...");
    for(iter = hash->Verticies.begin(); iter != hash->Verticies.end(); ++iter) {
        output_timer("Searching for some text...");
        std::vector<string>::iterator it_word = std::find_if(wordList.begin(), wordList.end(), [aho_corasick, iter](const string s) {
            string text = iter->first;
            string keyword = s;
            int state = 0;
            //std::cout << "Processing '" << text << "'..." << endl;
            for(int i = 0; i < text.size(); ++i) {
                //std::cout << "Processing letter '" << text[i] << "'..." << endl;
                state = aho_corasick->next_state(state, text[i], 'a');
                if(aho_corasick->get_bit_list()[state] == 0) continue;
                //std::cout << "Processing keyword '" << keyword << "'..." << endl;

                return (aho_corasick->get_bit_list()[state] & (1 << 0)) != 0;
            }
            //std::cout << "Processed '" << iter->first << "'." << endl;
            return false;
        });
        output_timer("Adding all found verticies to the graph...");
        while (it_word != wordList.end()) {
            if(hash->Verticies[it_word->c_str()] == nullptr) {
                hash->Verticies[it_word->c_str()] = new LinkedList<string>(it_word->c_str());
            }
            if(hash->Verticies[it_word->c_str()]->head->GetHeadString() != iter->first) {
                hash->Verticies[it_word->c_str()]->Add(iter->first);
            }
            it_word++;
        }
    }

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

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

но я отвлекся! Я считаю, что может быть решение, если я объединю ваше решение (@templatetypedef) с математикой государственной машины, которая стоит за вызовом aho_corasick, который я приписываю "andmej" на GitHub, помогая мне развиваться. Он полностью реализовал алгоритм Aho-Corasick здесь:

https://gist.github.com/andmej/1233426

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

Я довольно близко. Технически, это задание для работы интервью. Так что, я не обязательно нуждаюсь в помощи, но если вы предложите ее, я могу принять ее и рассмотреть ваши предложения, если я продолжу застрять. Я застрял на прошлой неделе. На каждом шагу появляется что-то новое, чего я не учел. Если вам интересно узнать больше об алгоритме, google-it и посмотрите на математику, которая участвует в управлении матрицей, которая управляет ребрами на графике. Это довольно сложный и чертовски бриллиант.

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

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

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

Амрит Коли Профессиональный Музыкант


вы не можете улучшить производительность цикла big-O. Однако существуют зависящие от алгоритма методы улучшения постоянного фактора, скрытого big-O, используя кэш.

вот пример улучшенного алгоритма транспонирования матрицы:Эффективная Программа Транспонирования Матрицы Кэша?

общей темой здесь является то, что мы фактически вводим больше петли, а не меньше.

Если вы должны ускорить цикл for любой ценой, посмотрите, можете ли вы найти параллелизирующий или векторизирующий компилятор и изменить его по мере необходимости, чтобы он воспользовался этим, или найти способ использовать некоторую библиотеку строительных блоков. См., например,http://en.wikipedia.org/wiki/Intel_C%2B%2B_Compiler и http://en.wikipedia.org/wiki/Math_Kernel_Library.

(или найдите лучший алгоритм-часто это даст вам что - то вроде следующий:

for (i = 0; i < LIMIT; i++) {
  // Do something clever with i 
  // that does not depend on j
  for (j = 0; j < LIMIT; j++) {
    // do something fast with i and j
    // and the results of the clever stuff
    // outside the loop over j
  }
}

)


Это зависит от того, нужны ли вам i и j во внутреннем цикле, например, иногда вы можете развернуть такая петля, как эта:

for (k = 0; k < LIMIT * LIMIT; ++k)
{
    // do something with k
}

но для всех, кроме самых тривиальных внутренних циклов, это, вероятно, не имеет ощутимого значения для производительности.

какую конкретную проблему вы на самом деле пытаетесь решить ?


я столкнулся с такой же проблемой некоторое время назад...

что вы думаете об этом? Одиночный цикл while (i-индекс внешнего цикла for-loop в вашем примере):

i = 0; j = 0;
while (i<M) {
  // Do something with i and j
  if (j<N-1) {
    j++;
  } else {
    j=0;
    i++;
  }
}