주문 번호의 효율적이고 안정적인 합계


12

부동 소수점 양수 ( std::vector<float>, size ~ 1000)의 목록이 꽤 있습니다. 숫자는 내림차순으로 정렬됩니다. 내가 순서대로 합하면 :

for (auto v : vec) { sum += v; }

나는 벡터의 끝 부분에 가까운 이후 나는 약간의 수치 안정성에 문제가있을 수 있습니다 생각 sum보다 훨씬 클 것이다 v. 가장 쉬운 해결책은 벡터를 역순으로 순회하는 것입니다. 내 질문은 : 앞으로뿐만 아니라 효율적입니까? 더 많은 캐시가 누락됩니까?

다른 스마트 솔루션이 있습니까?


1
속도 질문은 대답하기 쉽습니다. 그것을 벤치마킹하십시오.
Davide Spataro

정확도보다 속도가 더 중요합니까?
스타크

복제본은 아니지만 매우 유사한 질문 : float를 사용한 계열의 합계
acraig5075

4
음수에주의를 기울여야 할 수도 있습니다.
AProgrammer

3
실제로 높은 정도의 정밀도에 관심이 있다면 Kahan summation을 확인하십시오 .
맥스 랭 호프

답변:


3

수치 적 안정성 문제가있을 수 있습니다

테스트 해보십시오. 현재 가상의 문제가 있습니다. 즉, 전혀 문제가 없습니다.

테스트를 수행하고 가정이 실제 문제 로 구체화 되면 실제로 수정하는 것에 대해 걱정해야합니다.

즉, 부동 소수점 정밀도로 인해 문제 발생할 있지만 다른 모든 것보다 우선 순위를 부여하기 전에 실제로 데이터에 대한 여부를 확인할 수 있습니다.

... 더 많은 캐시가 누락됩니까?

1 천 개의 플로트는 4Kb입니다. 현대식 대량 시장 시스템의 캐시에 적합합니다 (다른 플랫폼을 염두에두고 있다면 그것이 무엇인지 알려주십시오).

유일한 위험은 프리 페 처가 뒤로 반복 할 때 도움이되지 않지만 물론 벡터가 이미 캐시에 있을 수 있다는 것입니다. 전체 프로그램의 맥락에서 프로파일 링 할 때까지 실제로이를 확인할 수 없으므로 전체 프로그램이있을 때까지 걱정할 필요가 없습니다.

다른 스마트 솔루션이 있습니까?

실제로 문제가 될 때까지 문제가 될 수있는 것에 대해 걱정하지 마십시오. 기껏해야 가능한 문제에 주목하고 코드를 구성하여 다른 모든 것을 다시 쓰지 않고도 가장 간단한 솔루션을 신중하게 최적화 된 솔루션으로 대체 할 수 있습니다.


5

I의 벤치 마크 사용 사례 및 결과는 루프에 대한 성능 차이 앞뒤로하지 않는 방향으로 점 (첨부 이미지 참조).

하드웨어 + 컴파일러에서도 측정 할 수 있습니다.


STL을 사용하여 합계를 수행하면 데이터를 수동으로 반복하는 것만 큼 빠르지 만 훨씬 표현력이 좋습니다.

역 축적에는 다음을 사용하십시오.

std::accumulate(rbegin(data), rend(data), 0.0f);

앞으로 누적하는 동안 :

std::accumulate(begin(data), end(data), 0.0f);

여기에 이미지 설명을 입력하십시오


그 웹 사이트는 정말 멋지다. 확실하게 : 당신은 무작위 생성을 타이밍하지 않습니까?
Ruggero Turra

아니요, state루프 의 부분 만 시간이 정해져 있습니다.
Davide Spataro

2

가장 쉬운 해결책은 벡터를 역순으로 순회하는 것입니다. 내 질문은 : 앞으로뿐만 아니라 효율적입니까? 더 많은 캐시가 누락됩니까?

예, 효율적입니다. 하드웨어의 지점 예측 및 스마트 캐시 전략은 순차적 액세스를 위해 조정됩니다. 벡터를 안전하게 축적 할 수 있습니다.

#include <numeric>

auto const sum = std::accumulate(crbegin(v), crend(v), 0.f);

2
이 맥락에서 "순차 액세스"는 앞으로, 뒤로 또는 둘 다를 의미합니까?
Ruggero Turra

1
@RuggeroTurra 소스를 찾을 수 없다면 지금은 CPU 데이터 시트를 읽을 수 없습니다.
YSC

@RuggeroTurra 일반적으로 순차적 액세스는 전달을 의미합니다. 모든 세미 메모리 프리 페처는 순차 순차 액세스를 포착합니다.
칫솔

@ 칫솔 감사합니다. 그래서, 루프 경우 것은 뒤로, 원칙적으로는 성능 문제가 될 수 있습니다
루 제로 Turra

원칙적으로, 적어도 일부 하드웨어에 전체 벡터가 아닌 경우 이미 L1 캐시한다.
쓸모없는

2

이를 위해 다음과 같이 조옮김없이 역 반복자를 사용할 수 있습니다 std::vector<float> vec.

float sum{0.f};
for (auto rIt = vec.rbegin(); rIt!= vec.rend(); ++rIt)
{
    sum += *rit;
}

또는 표준 알고리즘을 사용하여 동일한 작업을 수행하십시오.

float sum = std::accumulate(vec.crbegin(), vec.crend(), 0.f);

성능은 동일해야하며 벡터의 바이 패스 방향 만 변경해야합니다.


내가 틀렸다면 수정하지만 OP가 사용하는 foreach 문보다 오버 헤드가 발생하기 때문에 이것이 더 효율적이라고 생각합니다. 수치 안정성 부분에 대해서는 YSC가 맞습니다.
sephiroth

4
@sephiroth 아니요, 반쯤 괜찮은 컴파일러는 range-on 또는 iterator를 작성했는지 실제로 신경 쓰지 않습니다.
맥스 랭 호프

1
캐시 / 프리 페치로 인해 실제 성능이 동일하게 보장되지는 않습니다. OP가이를주의하는 것이 합리적입니다.
맥스 랭 호프

1

수치 적 안정성으로 정확성을 의미한다면, 정확도 문제가 생길 수 있습니다. 가장 큰 값과 가장 작은 값의 비율 및 결과의 정확성에 대한 요구 사항에 따라 문제가 될 수도 있고 아닐 수도 있습니다.

높은 정확도를 원한다면 Kahan summation 을 고려하십시오 -오류 보상을 위해 여분의 부동 소수점을 사용합니다. 또한이 페어의 합은 .

정확도와 시간 간의 트레이드 오프에 대한 자세한 분석은 이 기사를 참조 하십시오 .

C ++ 17 업데이트 :

다른 답변 중 일부는 언급했다 std::accumulate. C ++ 17부터 알고리즘을 병렬화 할 수 있는 실행 정책 이 있습니다.

예를 들어

#include <vector>
#include <execution>
#include <iostream>
#include <numeric>

int main()
{  
   std::vector<double> input{0.1, 0.9, 0.2, 0.8, 0.3, 0.7, 0.4, 0.6, 0.5};

   double reduceResult = std::reduce(std::execution::par, std::begin(input), std::end(input));

   std:: cout << "reduceResult " << reduceResult << '\n';
}

결정적이지 않은 반올림 오류로 인해 큰 데이터 세트를 더 빠르게 합산해야합니다 (사용자가 스레드 분할을 결정할 수 없다고 가정합니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.