카운트다운이 카운트다운보다 빠릅니까?
우리 컴퓨터 과학 선생님은 어떤 이유로 카운트 다운을 하는 것이 카운트 다운을 하는 것보다 효율적이라고 말한 적이 있다.예를 들어 FOR 루프를 사용해야 하며 루프 인덱스를 사용하지 않는 경우(예를 들어 화면에 N * 행을 인쇄하는 경우)는 다음과 같은 코드를 의미합니다.
for (i = N; i >= 0; i--)
putchar('*');
보다 나은 기능:
for (i = 0; i < N; i++)
putchar('*');
그게 정말 사실이니?만약 그렇다면, 그 이유를 아는 사람이 있나요?
그게 정말 사실이니?만약 그렇다면 이유를 아는 사람이 있나요?
고대에는 컴퓨터를 손으로 녹인 실리카에서 떼어낼 때, 8비트 마이크로 컨트롤러가 지구를 돌아다녔을 때, 그리고 선생님이 젊었을 때(또는 선생님의 선생님이 젊었을 때)에는 "감소하고 제로이면 건너뛰기(DSZ)"라고 불리는 일반적인 기계 명령이 있었습니다.핫샷 어셈블리프로그래머는 이 명령을 사용하여 루프를 구현했습니다.나중에 나온 기계들은 더 고급스러운 명령을 받았지만, 여전히 다른 것과 비교하는 것보다 0과 비교하는 것이 더 저렴한 프로세서가 꽤 있었습니다.(이것은 PPC나 SPARC와 같이 레지스터 전체가 항상 0이 되도록 예약되어 있는 일부 최신 RISC 머신에서도 마찬가지입니다).
를 0이 N
일이 요?슨슨일 이이?
- 레지스터를 저장할 수 있습니다.
- 더 작은 이진 인코딩을 사용하여 비교 지침을 받을 수 있습니다.
- 이전 명령이 플래그를 설정하는 경우(예를 들어 x86 패밀리 머신에서만) 명시적인 비교 명령이 필요하지 않을 수 있습니다.
이러한 차이로 인해 최신 고장 프로세서의 실제 프로그램이 현저하게 개선될 가능성이 있습니까?그럴 것 같지 않아요.사실 마이크로벤치마크에서도 측정 가능한 개선을 보여주면 감명받을 수 있을 것 같습니다.
요약: 나는 너의 선생님의 머리를 때린다!루프를 구성하는 방법에 대한 쓸모없는 의사 사실을 배우면 안 됩니다.루프의 가장 중요한 점은 루프가 종료되고 정답이 생성되며 읽기 쉽도록 하는 것입니다.너희 선생님이 신화 말고 중요한 것에 집중하셨으면 좋겠어.
카운터를 늘리거나 줄이는 것보다 훨씬 더 중요한 것은 메모리 업 또는 다운메모리 업입니다.대부분의 캐시는 다운 메모리가 아닌 업 메모리에 최적화되어 있습니다.메모리 액세스 시간은 오늘날 대부분의 프로그램에서 직면하는 병목현상이므로, 이는 메모리를 늘리기 위해 프로그램을 변경하면 카운터를 0이 아닌 값과 비교해야 하는 경우에도 퍼포먼스가 향상될 수 있음을 의미합니다.일부 프로그램에서는 코드를 다운이 아닌 업메모리로 변경함으로써 성능이 크게 향상되었습니다.
의심스럽다고요?메모리 업/다운 타임 루프를 위한 프로그램을 작성하기만 하면 됩니다.얻은 결과는 다음과 같습니다.
Average Up Memory = 4839 mus
Average Down Memory = 5552 mus
Average Up Memory = 18638 mus
Average Down Memory = 19053 mus
(여기서 "mus"는 마이크로초) 이 프로그램을 실행하지 않습니다.
#include <chrono>
#include <iostream>
#include <random>
#include <vector>
//Sum all numbers going up memory.
template<class Iterator, class T>
inline void sum_abs_up(Iterator first, Iterator one_past_last, T &total) {
T sum = 0;
auto it = first;
do {
sum += *it;
it++;
} while (it != one_past_last);
total += sum;
}
//Sum all numbers going down memory.
template<class Iterator, class T>
inline void sum_abs_down(Iterator first, Iterator one_past_last, T &total) {
T sum = 0;
auto it = one_past_last;
do {
it--;
sum += *it;
} while (it != first);
total += sum;
}
//Time how long it takes to make num_repititions identical calls to sum_abs_down().
//We will divide this time by num_repitions to get the average time.
template<class T>
std::chrono::nanoseconds TimeDown(std::vector<T> &vec, const std::vector<T> &vec_original,
std::size_t num_repititions, T &running_sum) {
std::chrono::nanoseconds total{0};
for (std::size_t i = 0; i < num_repititions; i++) {
auto start_time = std::chrono::high_resolution_clock::now();
sum_abs_down(vec.begin(), vec.end(), running_sum);
total += std::chrono::high_resolution_clock::now() - start_time;
vec = vec_original;
}
return total;
}
template<class T>
std::chrono::nanoseconds TimeUp(std::vector<T> &vec, const std::vector<T> &vec_original,
std::size_t num_repititions, T &running_sum) {
std::chrono::nanoseconds total{0};
for (std::size_t i = 0; i < num_repititions; i++) {
auto start_time = std::chrono::high_resolution_clock::now();
sum_abs_up(vec.begin(), vec.end(), running_sum);
total += std::chrono::high_resolution_clock::now() - start_time;
vec = vec_original;
}
return total;
}
template<class Iterator, typename T>
void FillWithRandomNumbers(Iterator start, Iterator one_past_end, T a, T b) {
std::random_device rnd_device;
std::mt19937 generator(rnd_device());
std::uniform_int_distribution<T> dist(a, b);
for (auto it = start; it != one_past_end; it++)
*it = dist(generator);
return ;
}
template<class Iterator>
void FillWithRandomNumbers(Iterator start, Iterator one_past_end, double a, double b) {
std::random_device rnd_device;
std::mt19937_64 generator(rnd_device());
std::uniform_real_distribution<double> dist(a, b);
for (auto it = start; it != one_past_end; it++)
*it = dist(generator);
return ;
}
template<class ValueType>
void TimeFunctions(std::size_t num_repititions, std::size_t vec_size = (1u << 24)) {
auto lower = std::numeric_limits<ValueType>::min();
auto upper = std::numeric_limits<ValueType>::max();
std::vector<ValueType> vec(vec_size);
FillWithRandomNumbers(vec.begin(), vec.end(), lower, upper);
const auto vec_original = vec;
ValueType sum_up = 0, sum_down = 0;
auto time_up = TimeUp(vec, vec_original, num_repititions, sum_up).count();
auto time_down = TimeDown(vec, vec_original, num_repititions, sum_down).count();
std::cout << "Average Up Memory = " << time_up/(num_repititions * 1000) << " mus\n";
std::cout << "Average Down Memory = " << time_down/(num_repititions * 1000) << " mus"
<< std::endl;
return ;
}
int main() {
std::size_t num_repititions = 1 << 10;
TimeFunctions<int>(num_repititions);
std::cout << '\n';
TimeFunctions<double>(num_repititions);
return 0;
}
다.sum_abs_up
★★★★★★★★★★★★★★★★★」sum_abs_down
일을 실시해, 같은 을 재고, 은 「수치」라고 하는 것 입니다.sum_abs_up
에 남다sum_abs_down
억은사사 사사사다다는 심지어 나나도 합격했다.vec
두 기능이 동일한 메모리 위치에 액세스하도록 참조합니다.그럼에도 불구하고,sum_abs_up
sum_abs_down
g+-O3로 하다.
내가 시간을 재는 루프가 얼마나 타이트한지 알아두는 것이 중요하다.루프의 본체가 클 경우 루프의 본체를 실행하는 데 걸리는 시간이 완전히 지배적이기 때문에 루프의 반복기가 메모리를 올리든 내리든 상관없습니다.또, 몇개의 드문 루프가 있는 경우는, 메모리의 다운이 업보다 고속인 경우도 있습니다.그러나 이러한 루프를 사용하더라도 메모리 업이 다운보다 항상 느린 것은 아닙니다(메모리를 업하는 작은 체형의 루프와는 반대되는 경우가 많습니다.실제로 몇 개의 루프를 타이밍으로 했을 경우, 메모리 업에 의한 퍼포먼스의 향상은 40+%였습니다.
경험에 비추어 볼 때, 루프 본체가 작고 루프가 다운되는 대신 메모리 업으로 가는 것과 거의 차이가 없다면 메모리를 업으로 하는 것이 좋습니다.
:vec_original
수 실험용으로 있습니다.sum_abs_up
★★★★★★★★★★★★★★★★★」sum_abs_down
vec
이러한 변경은 향후의 타이밍에 영향을 주지 않습니다.저는 이 게임들을 가지고 노는 것을 강력히 추천합니다.sum_abs_up
★★★★★★★★★★★★★★★★★」sum_abs_down
타이밍을 맞춰야 합니다.
C에서 psudo-assembly:
for (i = 0; i < 10; i++) {
foo(i);
}
로 바뀌다
clear i
top_of_loop:
call foo
increment i
compare 10, i
jump_less top_of_loop
한편:
for (i = 10; i >= 0; i--) {
foo(i);
}
로 바뀌다
load i, 10
top_of_loop:
call foo
decrement i
jump_not_neg top_of_loop
두 번째 psudo-assembly에서는 비교가 되지 않습니다.많은 아키텍처에는 점프에 사용할 수 있는 산술 연산(더하기, 빼기, 곱하기, 나누기, 증가, 감소)에 의해 설정된 플래그가 있습니다.이러한 정보에는, 조작 결과의 본질적인 비교와 0 의 무료가 되는 경우가 자주 있습니다.실제로 많은 아키텍처에서
x = x - 0
의 의미상 동일하다.
compare x, 0
또, 이 예에서 10과 비교하면, 코드가 더 나빠질 가능성이 있습니다.10은 레지스터에 상주해야 할 수 있습니다.따라서 공급 부족이 발생하면 루프를 통해 물건을 이동하거나 10을 새로고침하기 위한 추가 코드가 발생할 수 있습니다.
컴파일러는 이를 이용하기 위해 코드를 재배치할 수 있지만 루프를 통해 방향을 반전시키는 것이 의미적으로 동일한지 확신할 수 없는 경우가 많기 때문에 어려운 경우가 많습니다.
인텔 x86 명령어 세트에서는 통상 0이 아닌 종료 조건까지 카운트하는 루프보다 적은 명령으로 카운트다운을 실행할 수 있습니다.구체적으로는 ECX 레지스터는 전통적으로 x86 asm의 루프카운터로 사용되며 인텔 명령어 세트에는 ECX 레지스터의 제로를 테스트하고 테스트 결과에 따라 점프하는 특별한 jcxz 점프 명령이 있습니다.
단, 루프가 이미 클럭사이클 카운트에 매우 민감하지 않은 한 퍼포먼스 차이는 무시할 수 있습니다.0까지 카운트 다운하면 카운트 업에 비해 루프의 각 반복 클럭 사이클이 4~5회 줄어들기 때문에 유용한 기술이라기보다는 참신합니다.
또, 오늘날 최적화하는 컴파일러라면, 카운트 업 루프 소스 코드를 카운트 다운 해 제로 머신 코드로 변환할 수 있을 것입니다(루프 인덱스 변수를 어떻게 사용하는지에 따라 다름).따라서, 루프를 1-2 사이클씩 압축하는 것만으로 이상한 방법으로 쓸 필요는 없습니다.
네..!!!
하드웨어의 비교 처리 방법에서는 N에서0까지의 카운트가 0부터N까지의 카운트보다 약간 빨라집니다.
각 루프의 비교에 주의해 주세요.
i>=0
i<N
첫 는 기계어로 번역됩니다.
- 로드 i
- 0보다 작거나 같은 경우 비교 및 점프
두 .
- 로드 i
- 로드 N
- Sub i 및 N
- 0보다 작거나 같은 경우 비교 및 점프
카운트다운이나 카운트업 때문이 아니라하지만 당신의 코드가 기계 코드로 변환되는 방식 때문에..
에서 10까지 것과 .
i 것은 - i=100으로 세는 것은 i=0으로 100으로 세는 것보다 빠르다.대부분의 경우
i는 i에서 N=N까지의 계수보다 .
- 오늘날 컴파일러는 이 최적화를 대신 할 수 있습니다(스마트한 컴파일러라면).
- 또한 파이프라인은 Belady의 이상현상을 일으킬 수 있습니다(어떤 것이 더 좋을지 확신할 수 없습니다).
- 마지막으로 제시하신 루프에 대한 2가 동일하지 않음을 유의하시기 바랍니다.초판이 하나 더 인쇄되다
더 빠를 수 있어요.
현재 사용하고 있는 NIOS II 프로세서에서는 기존의 for 루프
for(i=0;i<100;i++)
그럼 어셈블리가 생성됩니다.
ldw r2,-3340(fp) %load i to r2
addi r2,r2,1 %increase i by 1
stw r2,-3340(fp) %save value of i
ldw r2,-3340(fp) %load value again (???)
cmplti r2,r2,100 %compare if less than equal 100
bne r2,zero,0xa018 %jump
카운트다운을 하면
for(i=100;i--;)
두 가지 지시사항을 덜 필요로 하는 어셈블리가 있습니다.
ldw r2,-3340(fp)
addi r3,r2,-1
stw r3,-3340(fp)
bne r2,zero,0xa01c
내부 루프가 많이 실행되는 중첩된 루프가 있는 경우 측정 가능한 차이를 얻을 수 있습니다.
int i,j,a=0;
for(i=100;i--;){
for(j=10000;j--;){
a = j+1;
}
}
내부 루프가 위와 같이 기록될 경우 실행 시간은 0.121999999999999734초입니다.내부 루프가 기존 방식으로 쓰여질 경우 실행 시간은 0.171999999999998623초입니다.루프의 카운트다운이 약 30% 빨라집니다.
단, 이 테스트는 모든 GCC 최적화를 끈 상태에서 수행되었습니다.컴파일러를 켜면 실제로 컴파일러는 이 핸디한 최적화보다 더 스마트하고 전체 루프 동안 레지스터에 값을 저장하여 다음과 같은 어셈블리를 얻을 수 있습니다.
addi r2,r2,-1
bne r2,zero,0xa01c
이 특별한 예에서는 컴파일러는 이 변수 a가 루프 실행 후 항상 1이 되어 루프를 모두 건너뛰는 것을 알아차립니다.
그러나 루프 본체가 충분히 복잡하면 컴파일러가 이 최적화를 할 수 없기 때문에 항상 고속 루프를 실행하는 가장 안전한 방법은 다음과 같이 기술하는 것입니다.
register int i;
for(i=10000;i--;)
{ ... }
물론 이것은 Betamoo가 말한 것처럼 루프가 거꾸로 실행되는 것이 문제가 되지 않는 한, 0까지 카운트다운 하는 경우에만 작동합니다.
방향에 관계없이 항상 접두사 형식을 사용합니다(i++가 아닌 +i).
for (i=N; i>=0; --i)
또는
for (i=0; i<N; ++i)
설명: http://www.eskimo.com/ ~scs/cclass/notes/sx7b.http://http://www.eskimo.com/
게다가 쓸 수 있다.
for (i=N; i; --i)
하지만 최신 컴파일러는 이러한 최적화를 정확하게 수행할 수 있을 것으로 기대합니다.
선생님께서 말씀하신 것은 별로 명확하지 않은 비꼬는 발언이었습니다.감소가 증가보다 빠른 것은 아니지만 감소와 함께 증가보다 훨씬 빠른 루프를 생성할 수 있습니다.
상세하게 설명하지 않고 루프 카운터 등을 사용할 필요가 없습니다.아래에서 중요한 것은 속도와 루프 수(제로 이외)입니다.
대부분의 사용자가 10회 반복하여 루프를 구현하는 방법은 다음과 같습니다.
int i;
for (i = 0; i < 10; i++)
{
//something here
}
99%의 경우 필요한 것은 하나뿐이지만 PHP, PYTON, JavaScript와 함께 CPU 틱이 정말로 중요한 모든 시간 크리티컬 소프트웨어(일반적으로 임베디드, OS, 게임 등)가 있으므로 어셈블리 코드를 간략히 살펴봅니다.
int i;
for (i = 0; i < 10; i++)
{
//something here
}
컴파일 후(최적화 없음) 컴파일 버전은 다음과 같습니다(VS2015).
-------- C7 45 B0 00 00 00 00 mov dword ptr [i],0
-------- EB 09 jmp labelB
labelA 8B 45 B0 mov eax,dword ptr [i]
-------- 83 C0 01 add eax,1
-------- 89 45 B0 mov dword ptr [i],eax
labelB 83 7D B0 0A cmp dword ptr [i],0Ah
-------- 7D 02 jge out1
-------- EB EF jmp labelA
out1:
루프 전체는 8개의 명령(26바이트)입니다.실제로는 6개의 명령(17바이트)과 2개의 브랜치가 있습니다.예 예 예 예 더 잘 할 수 있다는 것을 알고 있습니다(그것은 단지 예시일 뿐입니다).
이제 임베디드 개발자에 의해 작성되는 이 빈번한 구성을 생각해 보십시오.
i = 10;
do
{
//something here
} while (--i);
또한 10회 반복됩니다(예, i 값이 루프에 대해 표시된 값과 다르다는 것은 알지만 여기서는 반복 횟수에 유의합니다).이것은 다음과 같이 정리할 수 있습니다.
00074EBC C7 45 B0 01 00 00 00 mov dword ptr [i],1
00074EC3 8B 45 B0 mov eax,dword ptr [i]
00074EC6 83 E8 01 sub eax,1
00074EC9 89 45 B0 mov dword ptr [i],eax
00074ECC 75 F5 jne main+0C3h (074EC3h)
5개의 명령(18바이트)과 1개의 브랜치.실제로 루프에는 4개의 명령(11바이트)이 있습니다.
가장 좋은 점은 일부 CPU(x86/x64 호환 포함)는 레지스터를 감소시킬 수 있는 명령을 가지고 있다는 것입니다. 나중에 결과를 0과 비교하고 결과가 0과 다를 경우 분기를 수행합니다.사실상 모든 PC CPU가 이 명령을 구현합니다.이 루프를 사용하는 것은 실제로는 1개의 (네)2 바이트 명령일 뿐입니다.
00144ECE B9 0A 00 00 00 mov ecx,0Ah
label:
// something here
00144ED3 E2 FE loop label (0144ED3h) // decrement ecx and jump to label if not zero
어느 쪽이 빠른지 설명해야 하나요?
특정 CPU가 위의 명령어만 구현하지 않더라도 에뮬레이션에 필요한 것은 감소이며, 이전 명령어의 결과가 0일 경우 조건부 점프가 뒤따릅니다.
그래서 내가 왜 틀렸는지 등을 코멘트로 지적할 수 있는 경우에 관계없이 강조합니다.- 네, 방법, 이유, 타이밍을 알고 있다면 아래쪽으로 루프 다운하는 것이 좋습니다.
PS. 예, 적절한 최적화 레벨을 가진 Wise 컴파일러는 루프(상승 루프 카운터 포함)를 do로 고쳐 쓴다는 것을 알고 있습니다.한편, 일정한 루프 반복에 대해서는...(또는 펼치거나)...
을 할 입니다.i >= 0
와는 i
이치노
for (i = 5; i--;) {
alert(i); // alert boxes showing 4, 3, 2, 1, 0
}
와 모두i
을 사용하다
x86 명령의 수가 적은 이유에 대해서는, 다른 회답도 참조해 주세요.
어플리케이션에서 의미 있는 차이가 있는지 여부에 대해서는 루프의 수와 네스트 깊이에 따라 달라집니다.하지만 저는 이런 식으로 하는 것이 읽기 쉽기 때문에, 어쨌든 하고 있습니다.
어셈블러 레벨에서는 일반적으로 0까지 카운트되는 루프가 특정 값까지 카운트되는 루프보다 약간 빠릅니다.계산 결과가 0과 같으면 대부분의 프로세서가 제로 플래그를 설정합니다.1을 빼면 0이 지난 시점에서 계산 래핑이 이루어지면 일반적으로 반송 플래그가 변경되므로(일부 프로세서에서는 다른 프로세서에서는 플래그가 설정됩니다), 0과의 비교는 기본적으로 무료입니다.
반복 횟수가 상수가 아니라 변수인 경우에는 더욱 그렇습니다.
사소한 경우에는 컴파일러가 루프의 카운트 방향을 자동으로 최적화할 수 있지만 더 복잡한 경우에는 프로그래머가 루프의 방향이 전체적인 동작과 무관하다는 것을 알지만 컴파일러는 그것을 증명할 수 없다.
숫자의 에 대해 할 수 있는지에 따라 일이 할 수 해야 합니다.증가하는 루프를 사용하여 테스트해야 합니다.i<N
루프를 한 바퀴 돌 때마다. 반송 으로 설정)에 의해서, 뺄셈의 으로 설정되어 있는 것이, 의 부작용으로 됩니다.i>=0
이렇게 하면 루프 전체에 걸쳐 1회당 테스트가 절약됩니다.
실제로 현대의 파이프라인 프로세서 하드웨어에서는 명령에서 클럭 사이클까지 간단한 1-1 매핑이 없기 때문에 이 기능은 거의 무관합니다.(단, 마이크로컨트롤러에서 정확한 타이밍의 비디오 신호를 생성하는 등의 작업을 하고 있는 경우는 상정할 수 있습니다.그래도 어셈블리 언어로 작성해야 합니다.)
다음과 같은 경우 더 빨리 카운트다운하십시오.
for (i = someObject.getAllObjects.size(); i >= 0; i--) {…}
someObject.getAllObjects.size()
1번입니다.
같은 은, 「 」 「 」 「 」 「 」 「 」 「 」 「 」를 호출하는 것으로 실시할 수 있습니다.size()
피터가 언급했듯이, 루프를 벗어나야 합니다.
size = someObject.getAllObjects.size();
for (i = 0; i < size; i++) {…}
카운트다운이 업보다 빠릅니까?
그럴지도 모르죠. 하지만 99% 이상의 시간은 문제가 되지 않기 때문에 루프를 종료하기 위해 가장 '감지력 있는' 테스트를 사용해야 합니다. 분별력 있게 말하면, 루프가 무엇을 하고 있는지(무엇이 멈추는지를 포함) 독자의 최소한의 생각이 필요하다는 뜻입니다.코드를 코드 동작의 멘탈(또는 문서화된) 모델과 일치시킵니다.
루프가 동작하고 있는 경우는, 어레이(또는 리스트등)를 통해서도, 카운터가 증가하면, 루프가 어떻게 기능하고 있는지에 대한 독자의 생각과 일치합니다.이렇게 루프를 코드화합니다.
만약 지만 that가 있는 하고 있다.N
이치노카운터를 내려가는 것이 인지적으로 더 효과적일 수 있습니다.
답변의 '아마도'에 대한 자세한 내용은 다음과 같습니다.
대부분의 아키텍처에서 0이 되는 계산(또는 0에서 음으로 변경)에 대한 테스트는 명확한 테스트 명령이 필요하지 않습니다. 결과는 직접 확인할 수 있습니다.계산에 다른 숫자가 포함되는지 여부를 테스트하려면 일반적으로 명령 스트림에 해당 값을 테스트하기 위한 명시적 명령이 있어야 합니다.단, 특히 최신 CPU의 경우 이 테스트에서는 루프 구조에 노이즈 수준의 추가 시간이 적게 추가됩니다.특히 루프가 I/O를 실행하고 있는 경우.
한편, 0부터 카운트다운 해 카운터를 어레이 인덱스로 사용하는 경우, 예를 들면, 시스템의 메모리 아키텍처에 반하는 코드를 발견할 수 있습니다.메모리 읽기에서는 캐시가 시퀀셜 읽기를 예상하여 현재 메모리 위치보다 몇 개 앞에 있는 메모리 위치를 '앞을 내다보게' 됩니다.메모리를 통해 역방향으로 작업하는 경우 캐싱 시스템은 낮은 메모리 주소의 메모리 위치 읽기를 예상하지 못할 수 있습니다.이 경우 '뒤로' 루프하면 성능이 저하될 수 있습니다.단, 정확성이 가장 중요하기 때문에 (퍼포먼스가 문제가 되지 않는 한) 이 방법으로 루프를 코드화할 수 있습니다.코드와 모델을 일치시키는 것이 정확성을 확보하는 데 도움이 됩니다.잘못된 코드는 최대한 최적화되지 않습니다.
그래서 나는 코드의 성능이 정말로 중요하지 않으면 교수님의 조언을 잊는 경향이 있다(물론 그의 시험은 아니지만, 당신은 강의실에서는 여전히 실용적이어야 한다).
에는 "CPU"와 같은 DJNZ
== "0이 아니면 점프"이것에 의해, 레지스터에 초기 카운트치를 로드해, 1개의 명령으로 감소 루프를 효과적으로 관리할 수 있는 효율적인 루프가 가능하게 됩니다.대해 하고 있습니다이하는 분은, 선생님께서는 입니다.1980년의 ISA입니다.이 「경험의 법칙」이 최신의 CPU에 적용되고 있다고 생각하신다면, 선생님께서는 전혀 연락하고 있지 않은 것입니다.
밥.
마이크로 최적화를 실행할 때까지는 CPU 매뉴얼을 사용할 수 있습니다.게다가 그런 일을 하고 있다면, 어쨌든 이 질문을 할 필요는 없을지도 모릅니다. :-) 하지만 선생님께서는 그 생각에 동의하지 않는 것 같습니다.
루프 예에서는 다음 4가지 사항을 고려해야 합니다.
for (i=N;
i>=0; //thing 1
i--) //thing 2
{
putchar('*'); //thing 3
}
- 비교
비교는 (다른 사람이 지적한 바와 같이) 특정 프로세서 아키텍처와 관련이 있습니다.Windows 를 실행하는 프로세서보다 많은 타입의 프로세서가 있습니다.특히 0과의 비교를 단순화하고 속도를 높이는 명령이 있을 수 있습니다.
- 조정
위아래로 조정하는 것이 빠른 경우도 있습니다.일반적으로 우수한 컴파일러는 이를 파악하여 가능하면 루프를 다시 실행합니다.모든 컴파일러가 좋은 것은 아닙니다.
- 루프 본체
putchar를 사용하여 syscall에 액세스하고 있습니다.그것은 매우 느리다.또, (간접적으로) 화면에 렌더링 하고 있습니다.그것은 더 느리다.1000:1 이상이라고 생각합니다.이 상황에서는 루프 본체가 루프 조정/비교 비용을 완전히 초과합니다.
- 캐시
캐시 및 메모리 레이아웃은 성능에 큰 영향을 미칠 수 있습니다.이 상황에서는 상관없습니다.다만, 어레이에 액세스 하고 있어 최적의 퍼포먼스가 필요한 경우는, 사용의 컴파일러와 프로세서가 메모리 액세스를 어떻게 배치하고 있는지를 조사해, 그것을 최대한으로 활용할 수 있도록 소프트웨어를 조정하는 것이 중요합니다.일반적인 예는 행렬 곱셈과 관련하여 주어진 것입니다.
흥미로운 질문입니다만, 실제로는 중요하지 않고, 어느 쪽도 다른 쪽보다 낫다고는 생각하지 않습니다.
이 위키피디아 페이지에 따르면:윤초, "주로 조석 마찰로 인해 매 세기마다 태양일이 1.7ms 길어집니다."하지만 만약 여러분이 생일까지 날짜를 세고 있다면, 여러분은 정말로 이 작은 시간 차이를 신경쓰고 있나요?
소스코드가 읽기 쉽고 이해하기 쉽도록 하는 것이 더 중요합니다.이 두 가지 루프는 가독성이 왜 중요한지를 보여주는 좋은 예입니다. 같은 횟수만큼 반복되지 않습니다.
대부분의 프로그래머는 (i = 0; i < N; i++)를 읽고 이것이 N번 반복된다는 것을 즉시 이해할 수 있을 것입니다.(i = 1; i < = N; i++)의 루프는 어쨌든 나에게는 조금 덜 명확하고 (i = N; i > 0; i--)는 잠시 생각해 봐야 한다.아무 생각 없이 코드의 의도가 뇌에 직접 들어가는 것이 가장 좋습니다.
이상하게도 차이가 있는 것 같습니다.적어도 PHP에서는.다음 벤치마크를 고려해 주십시오.
<?php
print "<br>".PHP_VERSION;
$iter = 100000000;
$i=$t1=$t2=0;
$t1 = microtime(true);
for($i=0;$i<$iter;$i++){}
$t2 = microtime(true);
print '<br>$i++ : '.($t2-$t1);
$t1 = microtime(true);
for($i=$iter;$i>0;$i--){}
$t2 = microtime(true);
print '<br>$i-- : '.($t2-$t1);
$t1 = microtime(true);
for($i=0;$i<$iter;++$i){}
$t2 = microtime(true);
print '<br>++$i : '.($t2-$t1);
$t1 = microtime(true);
for($i=$iter;$i>0;--$i){}
$t2 = microtime(true);
print '<br>--$i : '.($t2-$t1);
결과는 흥미롭다.
PHP 5.2.13
$i++ : 8.8842368125916
$i-- : 8.1797409057617
++$i : 8.0271911621094
--$i : 7.1027431488037
PHP 5.3.1
$i++ : 8.9625310897827
$i-- : 8.5790238380432
++$i : 5.9647901058197
--$i : 5.4021768569946
이유를 아는 사람이 있다면 알아두면 좋을 텐데:)
EDIT: 0부터가 아니라 다른 임의의 값부터 계산해도 결과는 동일합니다.그럼 0에 대한 비교만이 차이를 만드는 것이 아닐까요?
아니, 그건 사실이 아니야.보다 고속으로 할 수 있는 상황은 루프가 반복될 때마다 함수를 호출하여 경계를 체크하는 경우입니다.
for(int i=myCollection.size(); i >= 0; i--)
{
...
}
하지만 그렇게 하는 것이 덜 명확하다면, 그것은 가치가 없습니다.현대 언어에서는 가능하면 포어치 루프를 사용해야 합니다.인덱스가 필요하지 않을 때 foreach 루프를 사용해야 하는 경우를 구체적으로 언급했습니다.
이제 조립 강의는 충분히 하셨다고 생각합니다:) 톱다운 접근의 또 다른 이유를 제시하겠습니다.
위에서부터 가는 이유는 매우 간단하다.루프 본체에서는 실수로 경계를 변경하여 잘못된 동작 또는 비종단 루프가 발생할 수 있습니다.
Java 코드의 작은 부분을 봐 주세요(이 때문에 언어는 문제가 되지 않습니다).
System.out.println("top->down");
int n = 999;
for (int i = n; i >= 0; i--) {
n++;
System.out.println("i = " + i + "\t n = " + n);
}
System.out.println("bottom->up");
n = 1;
for (int i = 0; i < n; i++) {
n++;
System.out.println("i = " + i + "\t n = " + n);
}
그러니까 위에서 아래로 가는 것을 선호하거나 상수를 경계로 갖는 것을 고려해야 한다는 것입니다.
언급URL : https://stackoverflow.com/questions/2823043/is-it-faster-to-count-down-than-it-is-to-count-up
'programing' 카테고리의 다른 글
관리자 아래의 직원 수를 가져오기 위한 중첩 쿼리 (0) | 2023.01.17 |
---|---|
MySQL - 그룹에 의해 반환되는 행을 제어합니다. (0) | 2023.01.17 |
마리아에서 열 이름 변경DB (0) | 2023.01.17 |
mysql error 1364 필드에 기본값이 없습니다. (0) | 2023.01.17 |
MySQL 빈 집합의 동작 오류 (0) | 2023.01.17 |