Влияет ли порядок наблюдений в операторе switch на производительность?

Вы не должны использовать implode. Это помещает разделенный запятыми список всего в форме в каждую строку, которую вы вставляете, и повторяет это для каждого проверенного окна. Вы должны просто вставить один элемент в каждую строку, индексируя массивы.

Однако, если у вас есть флажок в форме, он отправляет только те, которые отмечены. Результатом этого является то, что индексы массива $_POST['checkbox'] не будут совпадать с соответствующими элементами $_POST['item'] и $_POST['quantity']. Вам нужно поместить явные индексы в имена checkbox, чтобы вы могли связать их.

<form method = "POST">

<input type = "hidden" name = "item[]" value = "cupcake">
<input type = "text" name = "items" value = "cupcake" readonly><br>
<b>Price :</b> <span name = "price" value = "3.00">$17.00</span><br>
Quantity: <input tabindex="1" name="quantity[]" min="0" max="5" type="number" class="quantity" value="1" /><br>
<input tabindex="1" name="checkbox[0]" type="checkbox" value="17" /><span>Add to Cart</span></label></div></div></td><br>

<input type = "hidden" name = "item[]" value = "cake">
<input type = "text" name = "items" value = "cake" readonly><br>
<b>Price :</b> <span name = "price" value = "20.00">$20.00</span><br>
Quantity: <input tabindex="1" name="quantity[]" min="0" max="5" type="number" class="quantity" value="1" /><br>
<input tabindex="1" name="checkbox[1]" type="checkbox" value="20" /><span>Add to Cart</span></label></div></div></td><br>

<input type = "submit" name = "insertBT"><br>
</form>

Тогда ваш PHP-код может быть таким:

$stmt = $conn->prepare("INSERT INTO purchases (Product, Quantity, Price) VALUES (?, ?, ?)");
$stmt->bind_param("sis", $name, $quantity, $price);
foreach ($_POST['checkbox'] as $i => $price) {
    $name = $_POST['name'][$i];
    $quantity = $_POST['quantity'][$i];
    $stmt->execute();
}

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

Также обратите внимание, что в исходном коде вы открыли соединение с базой данных с помощью MySQLi, но затем вы попытались сделать вставку, используя mysql_query вместо $conn->query(). Вы не можете смешивать такие API; myql_query может использоваться только при открытии соединения с mysql_connect.

29
задан msc 10 November 2017 в 05:36
поделиться

7 ответов

Вы смотрите на неоптимизированный код, поэтому изучение его на предмет производительности не очень важно. Если вы посмотрите на оптимизированный код для своих примеров, вы обнаружите, что он вообще не выполняет сравнения! Оптимизатор замечает, что переменная переключения sc всегда имеет значение 1, поэтому он удаляет недостижимый case 2.

Оптимизатор также видит, что переменная a не используется после назначения, поэтому он также удаляет код из case 1, оставляя main() пустую функцию. И он удаляет функцию пролог / эпилог, которая манипулирует rbp, так как этот регистр не используется.

Таким образом, оптимизированный код заканчивается одинаково для любой версии вашей функции main():

main:
    xor eax, eax
    ret

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

Будет ли порядок case иметь значение в более реальном примере, где код фактически генерируется и используется? Возможно нет. Обратите внимание, что даже в неоптимизированном сгенерированном коде обе версии проверяют два значения case в числовом порядке, проверяя сначала для 1, а затем для 2, независимо от порядка в исходном коде. , Очевидно, что компилятор выполняет некоторую сортировку даже в неоптимизированном коде.

Обязательно обратите внимание на комментарии Гленна и Лундина: порядок разделов case - не единственное изменение между вашими двумя примерами, реальный код также отличается. В одном из них значения case соответствуют значениям, установленным в a, но не так в другом.

Компиляторы используют различные стратегии для операторов switch / case в зависимости от используемых фактических значений. Они могут использовать ряд сравнений, как в этих примерах, или, возможно, таблицу переходов. Может быть интересно изучить сгенерированный код, но, как всегда, если производительность имеет значение, следите за настройками оптимизации и тестируйте в реальной ситуации.

57
ответ дан Michael Geary 10 November 2017 в 05:36
поделиться

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

В вашем первом примере в случае 1 получается a = 1, а в случае 2 - a = 2. Компилятор может оптимизировать это, чтобы установить a = sc для этих двух случаев, что является одним оператором.

Во втором примере в случае 1 получается a = 2, а в случае 2 - a = 1. Компилятор больше не может использовать этот ярлык, поэтому он должен явно установить a = 1 или a = 2 для обоих случаев. Конечно, для этого нужно больше кода.

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

Вы можете проверить эту оптимизацию, используя код

int main()
{
    int a, sc = 1;

    switch (sc)
    {
        case 1:
        case 2:
            a = sc;
            break;
    }
}

, который также должен давать точно такой же ассемблер.

Кстати, ваш тестовый код предполагает, что sc действительно читается. Большинство современных оптимизирующих компиляторов способны обнаружить, что sc не изменяется между присваиванием и оператором switch, и заменить чтение sc постоянным значением 1. Дальнейшая оптимизация затем удалит избыточную ветвь (и) оператора switch, а затем даже назначение может быть оптимизировано, потому что на самом деле не изменяется. И с точки зрения переменной a, компилятор может также обнаружить, что a не читается в другом месте, и полностью удалить эту переменную из кода.

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

4
ответ дан Graham 10 November 2017 в 05:36
поделиться

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

6
ответ дан Thilo 10 November 2017 в 05:36
поделиться

Оптимизация компилятора операторов switch является сложной задачей. Конечно, вам нужно включить оптимизацию (например, попробуйте скомпилировать ваш код с gcc -O2 -fverbose-asm -S с GCC и заглянуть в сгенерированный файл .s ассемблера). Кстати, в обоих ваших примерах мой GCC 7 на Debian / Sid / x86-64 дает просто:

        .type   main, @function
main:
.LFB0:
        .cfi_startproc
# rsp.c:13: }
        xorl    %eax, %eax      #
        ret
        .cfi_endproc

(поэтому в этом сгенерированном коде нет никаких следов switch)

Если вам нужно понять, как компилятор может оптимизировать switch, есть несколько статей на эту тему, например, , эта .

Если у меня будет больше случаев переключения, то порядок падений влияет на производительность?

Не в целом, если вы используете какой-то оптимизирующий компилятор и просить его оптимизировать. Смотрите также этот .

Если это так важно для вас (но это не так, оставьте микрооптимизации для вашего компилятора!), Вам нужно провести тестирование, профилировать и, возможно, изучить сгенерированный ассемблерный код. Кстати, кеширование пропускает и распределение регистров может иметь гораздо большее значение, чем порядок case -s, поэтому я думаю, что вам вообще не стоит беспокоиться. Имейте в виду приблизительные временные оценки современных компьютеров. Поместите case в наиболее читаемый порядок (для следующего следующего разработчика , работающего над тем же исходным кодом). Читайте также о резьбовом коде . Если у вас есть объективные (связанные с производительностью) причины для переупорядочения case -ов (что очень маловероятно и должно произойти не чаще одного раза в жизни), напишите хороший комментарий, объясняющий эти причины.

Если вы так сильно заботитесь о производительности, обязательно выполните тесты и профиля , выберите хороший компилятор и используйте его с соответствующими параметрами оптимизации. Возможно, поэкспериментируйте с несколькими различными настройками оптимизации (и, возможно, с несколькими компиляторами). Вы можете добавить -march=native (в дополнение к -O2 или -O3). Вы можете рассмотреть возможность компиляции и связывания с -flto -O2, чтобы включить оптимизацию во время связи и т. Д. Вы также можете захотеть оптимизации на основе профиля .

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

(Не забудьте учесть затраты на разработку; в большинстве случаев они стоят намного больше)

16
ответ дан Basile Starynkevitch 10 November 2017 в 05:36
поделиться

В тех случаях, когда большинство меток case являются последовательными, компиляторы часто обрабатывают операторы switch, чтобы использовать таблицы переходов, а не сравнения. Точные средства, с помощью которых компиляторы решают, какую форму вычисляемого перехода использовать (если есть), будут различаться в разных реализациях. Иногда добавление дополнительных случаев в оператор switch может повысить производительность за счет упрощения сгенерированного кода компилятором (например, если код использует случаи 4-11, тогда как случаи 0-3 обрабатываются по умолчанию, добавление явного case 0:; case 1:; case 2:; case 3:; до default: может В результате компилятор сравнивает операнд с 12 и, если он меньше, использует таблицу переходов из 12 элементов. Если пропустить эти случаи, компилятор может вычесть 4 перед сравнением разницы с 8, а затем использовать таблицу из 8 элементов.

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

if (x==0)
  y++;
else switch(x)
{
  ...
}

«умный» компилятор может признать, что изменение кода на:

switch(x)
{
  case 0:
    y++;
    break;
  ...
}

может исключить сравнение во всех случаях, когда x не равен нулю , за счет вычисленного прыжка w курица x равна нулю. Если x большую часть времени не равен нулю, это была бы хорошая сделка. Однако, если x равен нулю 99,9% времени, это может быть плохой сделкой. Разные авторы компиляторов различаются по степени, в которой они будут пытаться оптимизировать конструкции, подобные первым, в последние.

5
ответ дан supercat 10 November 2017 в 05:36
поделиться

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

Этот пример показывает, что даже если вы измените порядок наблюдений в операторе switch в вашем примере, GCC и большинство других компиляторов будут переупорядочивать их, если включены оптимизации. Я использовал функции extern, чтобы убедиться, что значения известны только во время выполнения, но я также мог бы использовать, например, rand.

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

4
ответ дан user8880145 10 November 2017 в 05:36
поделиться

Оператор switch обычно составляется с помощью таблиц переходов , а не с помощью простых сравнений.

Таким образом, нет никаких потерь в производительности, если вы переставляете описания случаев.

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

Когда различия чисел между делом number велики от одного случая к другому, например, в case 10: и case 200000: компилятор, безусловно, не будет генерировать таблицы переходов, поскольку он должен заполнить около 200К записей почти все с указатель на случай default:, и в этом случае он будет использовать сравнения.

5
ответ дан alinsoar 10 November 2017 в 05:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: