Какая из этих частей кода быстрее в Java?

Вот некоторый пример кода. Я думаю, что это - то, что Вы ищете. Следующие дисплеи точно то же в (Mac) Firefox 3 и IE7.

#absdiv {
  position: absolute; 
  left: 100px; 
  top: 100px; 
  width: 80%; 
  height: 60%; 
  background: #999;
}

#pctchild {
  width: 60%; 
  height: 40%; 
  background: #CCC;
}

#reldiv {
  position: relative;
  left: 20px;
  top: 20px;
  height: 25px;
  width: 40%;
  background: red;
}
<div id="absdiv">
    <div id="reldiv"></div>
    <div id="pctchild"></div>
</div>
11
задан Holloway 10 December 2015 в 19:58
поделиться

14 ответов

When you get down to the lowest level (machine code but I'll use assembly since it maps one-to-one mostly), the difference between an empty loop decrementing to 0 and one incrementing to 50 (for example) is often along the lines of:

      ld  a,50                ld  a,0
loop: dec a             loop: inc a
      jnz loop                cmp a,50
                              jnz loop

That's because the zero flag in most sane CPUs is set by the decrement instruction when you reach zero. The same can't usually be said for the increment instruction when it reaches 50 (since there's nothing special about that value, unlike zero). So you need to compare the register with 50 to set the zero flag.


However, asking which of the two loops:

for(int i = 100000; i > 0; i--) {}
for(int i = 1; i < 100001; i++) {}

is faster (in pretty much any environment, Java or otherwise) is useless since neither of them does anything useful. The fastest version of both those loops no loop at all. I challenge anyone to come up with a faster version than that :-)

They'll only become useful when you start doing some useful work inside the braces and, at that point, the work will dictate which order you should use.

For example if you need to count from 1 to 100,000, you should use the second loop. That's because the advantage of counting down (if any) is likely to be swamped by the fact that you have to evaluate 100000-i inside the loop every time you need to use it. In assembly terms, that would be the difference between:

     ld  b,100000             dsw a
     sub b,a
     dsw b

(dsw is, of course, the infamous do something with assembler mnemonic).

Since you'll only be taking the hit for an incrementing loop once per iteration, and you'll be taking the hit for the subtraction at least once per iteration (assuming you'll be using i, otherwise there's little need for the loop at all), you should just go with the more natural version.

If you need to count up, count up. If you need to count down, count down.

69
ответ дан 3 December 2019 в 00:37
поделиться

Ответ - (как вы, вероятно, узнали на веб-сайте)

Я думаю, причина в том, что условие i> 0 для завершения цикла выполняется быстрее тестировать.

0
ответ дан 3 December 2019 в 00:37
поделиться

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

Если нет причин делать это иначе. Помните, что приложения Java не всегда работают на HotSpot и / или быстром оборудовании.

3
ответ дан 3 December 2019 в 00:37
поделиться

Что касается тестирования нуля в JVM: очевидно, это можно сделать с помощью ifeq , тогда как для тестирования чего-либо еще требуется if_icmpeq , что также включает в себя установку дополнительное значение в стеке.

Тестирование для > 0 , как в вопросе, может быть выполнено с помощью ifgt , тогда как тестирование для <100001 будет нужен if_icmplt .

2
ответ дан 3 December 2019 в 00:37
поделиться

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

Предположим, что ваш компилятор не настолько умен или у вас действительно нет пустого тела цикла: Аргумент «счетчик обратного цикла» имеет смысл для некоторых языков ассемблера (он может иметь смысл и для байтового кода Java, я не знаю его конкретно). Однако компилятор очень часто может преобразовать ваш цикл для использования счетчиков уменьшения. Если у вас нет тела цикла, в котором явно используется значение i, компилятор может выполнить это преобразование. И снова вы часто не видите разницы.

2
ответ дан 3 December 2019 в 00:37
поделиться

A better question is;

Which is easier to understand/work with?

This is far more important than a notional difference in performance. Personally, I would point out that performance shouldn't be the criteria for determining the difference here. If they didn't like me challenging their assumption on this, I wouldn't be unhappy about not getting the job. ;)

13
ответ дан 3 December 2019 в 00:37
поделиться

On a modern Java implementation this is not true. Summing up the numbers up to one billion as a benchmark:

Java(TM) SE Runtime Environment 1.6.0_05-b13
Java HotSpot(TM) Server VM 10.0-b19
up 1000000000: 1817ms 1.817ns/iteration (sum 499999999500000000)
up 1000000000: 1786ms 1.786ns/iteration (sum 499999999500000000)
up 1000000000: 1778ms 1.778ns/iteration (sum 499999999500000000)
up 1000000000: 1769ms 1.769ns/iteration (sum 499999999500000000)
up 1000000000: 1769ms 1.769ns/iteration (sum 499999999500000000)
up 1000000000: 1766ms 1.766ns/iteration (sum 499999999500000000)
up 1000000000: 1776ms 1.776ns/iteration (sum 499999999500000000)
up 1000000000: 1768ms 1.768ns/iteration (sum 499999999500000000)
up 1000000000: 1771ms 1.771ns/iteration (sum 499999999500000000)
up 1000000000: 1768ms 1.768ns/iteration (sum 499999999500000000)
down 1000000000: 1847ms 1.847ns/iteration (sum 499999999500000000)
down 1000000000: 1842ms 1.842ns/iteration (sum 499999999500000000)
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000)
down 1000000000: 1832ms 1.832ns/iteration (sum 499999999500000000)
down 1000000000: 1842ms 1.842ns/iteration (sum 499999999500000000)
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000)
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000)
down 1000000000: 1847ms 1.847ns/iteration (sum 499999999500000000)
down 1000000000: 1839ms 1.839ns/iteration (sum 499999999500000000)
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000)

Note that the time differences are brittle, small changes somewhere near the loops can turn them around.

Edit: The benchmark loops are

        long sum = 0;
        for (int i = 0; i < limit; i++)
        {
            sum += i;
        }

and

        long sum = 0;
        for (int i = limit - 1; i >= 0; i--)
        {
            sum += i;
        }

Using a sum of type int is about three times faster, but then sum overflows. С BigInteger он работает более чем в 50 раз медленнее:

BigInteger up 1000000000: 105943ms 105.943ns/iteration (sum 499999999500000000)
10
ответ дан 3 December 2019 в 00:37
поделиться

Циклы идентичны, за исключением одной критической части:

i> 0; а также i < 100001;

The greater than zero check is done by checking the NZP (Commonly known as condition code or Negative Zero or Positive bit) bit of the computer.

The NZP bit is set whenever operation such as load, AND, addition ect. are performed.

The greater than check cannot directly utilize this bit (and therefore takes a bit longer...) The general solution is to make one of the values negative (by doing a bitwise NOT and then adding 1) and then adding it to the compared value. If the result is zero, then they're equal. Positive, then the second value (not the neg) is greater. Negative, then the first value (neg) is greater. This check takes a slightly longer than the direct nzp check.

I'm not 100% certain that this is the reason behind it though, but it seems like a possible reason...

1
ответ дан 3 December 2019 в 00:37
поделиться

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

Тем не менее, разница, вероятно, связана с лежащим в основе инструкции, которые создаются для сравнения. Проверка того, что значение равно 0, - это просто вентиль И-НЕ ИЛИ-ИЛИ. В то время как для проверки того, равно ли значение произвольной константе, требуется загрузить эту константу в регистр, а затем сравнить два регистра. (Это, вероятно, потребует дополнительных задержек затвора или двух.) Тем не менее, с конвейерной обработкой и современными ALU я был бы удивлен, если бы различие было значительным с самого начала.

0
ответ дан 3 December 2019 в 00:37
поделиться

On many compilers, the machine instructions emitted for a loop going backwards, are more efficient, because testing for zero (and therefore zero'ing a register) is faster than a load immediate of a constant value.

On the other hand, a good optimising compiler should be able to inspect the loop inner and determine that going backwards won't cause any side effects...

BTW, that is a terrible interview question in my opinion. Unless you are talking about a loop which runs 10 millions of times AND you have ascertained that the slight gain is not outweighed by many instances of recreating the forward loop value (n - i), any performance gain will be minimal.

As always, don't micro-optimise without performance benchmarking and at the expense of harder to understand code.

23
ответ дан 3 December 2019 в 00:37
поделиться

Обычно реальный код выполняется быстрее, счетчик увеличивается. Для этого есть несколько причин:

  • Процессоры оптимизированы для прямого чтения памяти.
  • HotSpot (и, предположительно, другие компиляторы байт-кода-> машинные) сильно оптимизируют прямые циклы, но не беспокойтесь об обратных циклах, потому что они случаются так нечасто.
  • Вверх обычно более очевиден, а более чистый код часто быстрее.

Так что счастливое выполнение правильных действий обычно будет быстрее. Ненужная микрооптимизация - зло. Я специально не писал обратные циклы с момента программирования ассемблера 6502.

6
ответ дан 3 December 2019 в 00:37
поделиться

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

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

Пишите четкий код вместо того, чтобы пытаться быть умным.

3
ответ дан 3 December 2019 в 00:37
поделиться

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

Позвольте компилятору сделать то, для чего он нужен, и прояснить ваше намерение (как для компилятора, так и для читателя). Другая распространенная пессимизация Java:

public final static String BLAH = new StringBuilder().append("This is ").append(3).append(' text").toString();

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

public final static String BLAH = "This is a " + 3 + " test";

где он не оптимизирует первую, а вторую легче читать.

А как насчет (a> b)? A: b vs Math.max (a, б) ? Я знаю, что лучше прочитаю вторую, поэтому мне все равно, что первая не требует накладных расходов на вызов функции.

В этом списке есть пара полезных вещей, например, знание того, что finally [ Блок 1117676] не вызывается в System.exit () потенциально полезен. Полезно знать, что деление числа с плавающей запятой на 0,0 не вызывает исключения.

Но не беспокойтесь о компиляторе, если это действительно не имеет значения (и держу пари, что в 99,99% случаев это не так).

exit ()
является потенциально полезным. Полезно знать, что деление числа с плавающей запятой на 0,0 не вызывает исключения.

Но не беспокойтесь о компиляторе, если это действительно не имеет значения (и держу пари, что в 99,99% случаев это не так).

exit () потенциально полезен. Полезно знать, что деление числа с плавающей запятой на 0,0 не вызывает исключения.

Но не беспокойтесь о компиляторе, если это действительно не имеет значения (и держу пари, что в 99,99% случаев это не так).

17
ответ дан 3 December 2019 в 00:37
поделиться

Есть только два способа ответить на этот вопрос.

  1. Сказать вам, что это действительно, действительно не имеет значения, и вы тратите время, даже задаваясь вопросом.

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

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

Итак, я сделал вам эталонный бенчмарк, который вы можете попробовать здесь:

http://code.google.com/p/caliper/source/browse/trunk/test/examples/LoopingBackwardsBenchmark.java

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

     max benchmark        ns
       2  Forwards         4
       2 Backwards         3
      20  Forwards         9
      20 Backwards        20
    2000  Forwards      1007
    2000 Backwards      1011
20000000  Forwards   9757363
20000000 Backwards  10303707

Петля в обратном направлении выглядит как победа для кого-нибудь?

.
6
ответ дан 3 December 2019 в 00:37
поделиться
Другие вопросы по тегам:

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