Вот некоторый пример кода. Я думаю, что это - то, что Вы ищете. Следующие дисплеи точно то же в (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>
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.
Ответ - (как вы, вероятно, узнали на веб-сайте)
Я думаю, причина в том, что условие i> 0
для завершения цикла выполняется быстрее тестировать.
В основе таких вопросов лежат старые рекомендации. Все дело в сравнении: известно, что сравнение с 0 быстрее. Много лет назад это могло показаться очень важным. В настоящее время, особенно с Java, я бы предпочел позволить компилятору и виртуальной машине делать свою работу и сосредоточиться на написании кода, который легко поддерживать и понимать.
Если нет причин делать это иначе. Помните, что приложения Java не всегда работают на HotSpot и / или быстром оборудовании.
Что касается тестирования нуля в JVM: очевидно, это можно сделать с помощью ifeq , тогда как для тестирования чего-либо еще требуется if_icmpeq , что также включает в себя установку дополнительное значение в стеке.
Тестирование для > 0
, как в вопросе, может быть выполнено с помощью ifgt , тогда как тестирование для <100001
будет нужен if_icmplt .
Это самый глупый вопрос, который я когда-либо видел. Тело цикла пусто. Если компилятор хоть сколько-нибудь хорош, он просто не будет выдавать никакого кода. Он ничего не делает, не может генерировать исключение и ничего не изменяет за пределами своей области действия.
Предположим, что ваш компилятор не настолько умен или у вас действительно нет пустого тела цикла: Аргумент «счетчик обратного цикла» имеет смысл для некоторых языков ассемблера (он может иметь смысл и для байтового кода Java, я не знаю его конкретно). Однако компилятор очень часто может преобразовать ваш цикл для использования счетчиков уменьшения. Если у вас нет тела цикла, в котором явно используется значение i, компилятор может выполнить это преобразование. И снова вы часто не видите разницы.
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. ;)
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)
Циклы идентичны, за исключением одной критической части:
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...
Суть в том, что для любого приложения, не критичного к производительности, разница, вероятно, не имеет значения. Как отмечали другие, бывают случаи, когда использование ++ i вместо i ++ может быть быстрее, однако, особенно в циклах for, любой современный компилятор должен оптимизировать это различие.
Тем не менее, разница, вероятно, связана с лежащим в основе инструкции, которые создаются для сравнения. Проверка того, что значение равно 0, - это просто вентиль И-НЕ ИЛИ-ИЛИ. В то время как для проверки того, равно ли значение произвольной константе, требуется загрузить эту константу в регистр, а затем сравнить два регистра. (Это, вероятно, потребует дополнительных задержек затвора или двух.) Тем не менее, с конвейерной обработкой и современными ALU я был бы удивлен, если бы различие было значительным с самого начала.
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.
Обычно реальный код выполняется быстрее, счетчик увеличивается. Для этого есть несколько причин:
Так что счастливое выполнение правильных действий обычно будет быстрее. Ненужная микрооптимизация - зло. Я специально не писал обратные циклы с момента программирования ассемблера 6502.
Вы уверены, что интервьюер, задающий такой вопрос, ожидает прямого ответа («номер один быстрее» или «номер два быстрее»), или если этот вопрос задают, чтобы спровоцировать дискуссию, как это происходит в ответы, которые здесь дают люди?
В общем, невозможно сказать, какой из них быстрее, потому что он сильно зависит от компилятора Java, JRE, процессора и других факторов. Использование одного или другого в вашей программе только потому, что вы думаете, что один из них быстрее, без понимания деталей на самом низком уровне, является суеверным программированием . И даже если одна версия быстрее другой в вашей конкретной среде, разница, скорее всего, настолько мала, что не имеет значения.
Пишите четкий код вместо того, чтобы пытаться быть умным.
Вопросы такого рода в значительной степени неуместны и отвлекают некоторых людей. Назовите это Культ микрооптимизации или как хотите, но быстрее ли это зацикливаться вверх или вниз? Шутки в сторону? Вы используете то, что подходит для вашей работы. Вы не пишете свой код для сохранения двух тактовых циклов или чего-то еще.
Позвольте компилятору сделать то, для чего он нужен, и прояснить ваше намерение (как для компилятора, так и для читателя). Другая распространенная пессимизация 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% случаев это не так).
Есть только два способа ответить на этот вопрос.
Сказать вам, что это действительно, действительно не имеет значения, и вы тратите время, даже задаваясь вопросом.
Сказать вам, что единственный способ узнать это запуск надежного эталона на вашем реальном производственном оборудовании, OS и JRE установке, которая вам небезразлична.
Сказать вам, что единственный способ узнать это - запуск надежного эталона на вашем реальном производственном оборудовании, 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
Петля в обратном направлении выглядит как победа для кого-нибудь?
.