Как возможно, чтобы некоторый код занимал больше времени при заданных одних и тех же входных данных только потому, что он находится в цикле? - PullRequest
0 голосов
/ 23 мая 2018

Prelude / Context: я только начал изучать c ++ и решил написать некоторый код, который бы применял один вентиль кубита к квантовому регистру, где регистр хранится в массиве, называемом амплитудами, и четырех элементах одного кубита.Ворота а, б, в, д.Я попытался написать версию, в которой не было бы оператора if, который появился в моем первом проходе, и, к моему первому удовольствию, он, казалось, имел небольшое улучшение производительности (~ 10%).Если я изменю количество кубитов в регистре или кубит, на который я нацеливаю ворота, я получу аналогичный результат.Затем я попытался создать цикл, который бы выполнял сравнение времени для различных целевых кубитов, и произошло нечто очень странное (по крайней мере для меня).Альтернативная функция, которую я написал, избегает, чтобы оператор if удваивал время выполнения (с ~ 0,23 до 0,46 секунды), тогда как функция с оператором if не влияла на время выполнения (~ 0,25 секунды).Это приводит меня к моему вопросу:

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

Например, если я запускаю тест с 25 кубитами и целевым кубитом 1, функция «нет, если» побеждает.Затем, если я напишу цикл while, чтобы выполнить сравнение на 25 кубитах для каждого значения цели, начинающегося с 1, функция «no if» занимает вдвое больше времени, чтобы выполнить даже на первой итерации, когда она получает входные данные, идентичные предыдущему случаю.,Интересно, что если я просто включу цикл while и сделаю его бесконечным циклом while, поставив «True» в операторе while или закомментировав целевой оператор приращения + = 1, функция больше не займет двойное время.Это явление требует цикла и приращения от того, что я могу сказать.

Код ниже на случай, если это простая ошибка кодирования на новом языке, о котором я менее знаком.Я использую Visual Studio 2017 Community Edition со всеми настройками по умолчанию, за исключением того, что я использую сборку "release" для более быстрого выполнения кода.Закомментирование оператора while и соответствующей закрывающей фигурной скобки удваивает время «нет, если».

#include "stdafx.h"
#include <iostream>
#include <time.h>
#include <complex>

void matmulpnoif(std::complex<float> arr[], std::complex<float> out[], int numqbits, std::complex<float> a,
std::complex<float> b, std::complex<float> c, std::complex<float> d, int target)
{
    long length = 1 << (numqbits);
    long offset = 1 << (target - 1);
    long state = 0;
    while (state < length)
    {
        out[state] = arr[state] * a + arr[state + offset] * b;
        out[state + offset] = arr[state] * c + arr[state + offset] * d;
        state += 1 + offset * (((state%offset) + 1) / offset);
    }
}

void matmulpsingle(std::complex<float> arr[], std::complex<float> out[], int numqbits, std::complex<float> a,
std::complex<float> b, std::complex<float> c, std::complex<float> d, int target)
{
    long length = 1 << (numqbits);
    int shift = target - 1;
    long offset = 1 << shift;
    for (long state = 0; state < length; ++state)
    {
        if ((state >> shift) & 1)
        {
            out[state] = arr[state - offset] * c + arr[state] * d;
        }
        else
        {
            out[state] = arr[state] * a + arr[state + offset] * b;
        }
    }
}

int main()
{
    using namespace std;
    int numqbits = 25;
    long arraylength = 1 << numqbits;
    complex<float>* amplitudes = new complex<float>[arraylength];
    for (long i = 0; i < arraylength; ++i)
    {
        amplitudes[i] = complex<float>(0., 0.);
    }
    amplitudes[0] = complex<float>(1., 0.);
    complex<float> a(0., 0.);
    complex<float> b(1., 0.);
    complex<float> c(0., 0.);
    complex<float> d(1., 0.);
    int target = 1;
    int repititions = 10;
    clock_t startTime;
    //while (target <= numqbits) {
        startTime = clock();
        for (int j = 0; j < repititions; ++j) {
            complex<float>* outputs = new complex<float>[arraylength];
            matmulpsingle(amplitudes, outputs, numqbits, a, b, c, d, target);
            delete[] outputs;
        }
        cout << float(clock() - startTime) / (float)(CLOCKS_PER_SEC*repititions) << " seconds." << endl;
        startTime = clock();
        for (int k = 0; k < repititions; ++k) {
            complex<float>* outputs = new complex<float>[arraylength];
            matmulpnoif(amplitudes, outputs, numqbits, a, b, c, d, target);
            delete[] outputs;
        }
        cout << float(clock() - startTime) / (float)(CLOCKS_PER_SEC*repititions) << " seconds." << endl;
        target+=1;
    //}
    delete[] amplitudes;
    return 0;
}

1 Ответ

0 голосов
/ 23 мая 2018

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

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

Например, на моем компьютере (Linux, GCC 7.3.1), с включенным только -O3, matmulpnoif всегда быстрее (4,8 с против 2,4 с или 4,8 с против 4,2 с - это времяне измеряется с clock(), в зависимости от того, есть петля или нет).Если бы мне пришлось угадывать, что происходит в этом случае, компилятор мог бы понять, что offset всегда один, и оптимизировать оставшуюся операцию (деление - безусловно, самая дорогая операция, которую вы там выполняете).Тем не менее, это может быть комбинация и других вещей.

Еще одна вещь, которую стоит отметить, clock() НЕ следует использовать для измерения времени.Он подсчитывает количество тактов, например, если вы распараллелите код между двумя потоками, это число будет вдвое больше (при условии, что ваш код нигде не ждет - что на моем компьютере не так).Если вы хотите измерить время, я предлагаю вам взглянуть на <chrono>, high_resolution_clock поможет.

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

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

...