나는 벡터를 많이 좋아한다. 그들은 깔끔하고 빠릅니다. 그러나 나는 valarray라는 것이 존재한다는 것을 알고 있습니다. 왜 벡터 대신에 valarray를 사용해야합니까? 나는 valarrays에 약간의 구문 설탕이 있다는 것을 알고 있지만 그 외에는 언제 유용합니까?
나는 벡터를 많이 좋아한다. 그들은 깔끔하고 빠릅니다. 그러나 나는 valarray라는 것이 존재한다는 것을 알고 있습니다. 왜 벡터 대신에 valarray를 사용해야합니까? 나는 valarrays에 약간의 구문 설탕이 있다는 것을 알고 있지만 그 외에는 언제 유용합니까?
답변:
Valarray (값 배열)는 Fortran의 속도 중 일부를 C ++로 가져 오도록 고안되었습니다. 컴파일러는 포인터에 대한 valarray를 만들지 않으므로 컴파일러가 코드에 대한 가정을하고 더 잘 최적화 할 수 있습니다. (Fortran이 너무 빠른 주된 이유는 포인터 유형이 없으므로 포인터 별칭이 없기 때문입니다.)
Valarrays에는 표준의 일부가 약간 더 많은 작업을 사용할 수 있지만 합리적으로 쉬운 방법으로 클래스를 슬라이스 할 수있는 클래스가 있습니다. 그것들의 크기를 조정하는 것은 파괴적이며 반복자가 없습니다.
따라서 숫자로 작업하고 편리함이 중요한 용도로 사용되는 것은 아닙니다. 그렇지 않으면 벡터가 훨씬 더 편리합니다.
valarray
잘못된 시간에 잘못된 장소에서 태어난 고아의 일종입니다. 최적화, 특히 Crays와 같은 벡터 프로세서가 쓰여질 때 강력한 수학에 사용 된 기계에 대한 최적화 시도입니다.
벡터 프로세서의 경우 일반적으로 전체 작업에 단일 작업을 적용한 후 다음 작업을 전체 배열에 적용하는 등 필요한 작업을 모두 수행 할 때까지 계속했습니다.
그러나 아주 작은 배열을 다루지 않는 한 캐싱에는 제대로 작동하지 않는 경향이 있습니다. 대부분의 최신 컴퓨터에서 일반적으로 선호하는 것은 배열의 일부를로드하고 모든 작업을 수행 한 다음 배열의 다음 부분으로 이동하는 것입니다.
valarray
또한 앨리어싱 (aliasing) 가능성을 제거해야합니다. 적어도 이론적으로는 레지스터에 값을 더 자유롭게 저장할 수 있기 때문에 컴파일러가 속도를 향상시킬 수 있습니다. 그러나 실제로는 실제 구현이이 점을 어느 정도 활용할 수 있는지 확신 할 수 없습니다. 나는 그것이 일종의 닭과 계란 종류의 문제라고 생각합니다. 컴파일러 지원이 없으면 인기를 얻지 못했지만 인기가 없다면 컴파일러를 지원하는 데 어려움을 겪지는 않을 것입니다.
또한 valarray와 함께 사용할 어리석은 (문자 그대로) 보조 클래스가 있습니다. 당신은 얻을 slice
, slice_array
, gslice
그리고 gslice_array
(A)의 조각과 재생 valarray
, 그리고 그것은 다차원 배열처럼 행동합니다. 또한 mask_array
연산을 "마스크"해야합니다 (예 : x에 y의 항목을 추가하지만 z가 0이 아닌 위치에만). 을 사소하게 사용하려면 valarray
이 보조 클래스에 대해 많은 것을 배워야합니다. 그 중 일부는 꽤 복잡하고 그 중 어느 것도 매우 잘 문서화되지 않은 것 같습니다.
결론 : 그것은 광채의 순간을 가지고 꽤 깔끔하게 어떤 일을 할 수 있지만, 그것이 모호한 (그리고 거의 확실하게 남아있을) 매우 좋은 이유가 있습니다.
편집 (8 년 후, 2017 년) : 위의 일부 중 적어도 일부는 더 이상 사용되지 않습니다. 일례로, 인텔은 컴파일러에 최적화 된 버전의 valarray를 구현했습니다. 인텔 IPP (Integrated Performance Primitives)를 사용하여 성능을 향상시킵니다. 정확한 성능 향상은 의심 할 여지없이 다양하지만 간단한 코드를 사용한 빠른 테스트는 "표준"구현으로 컴파일 된 동일한 코드와 비교하여 속도가 약 2 : 1 개선되었음을 보여줍니다 valarray
.
따라서 C ++ 프로그래머가 valarray
많은 수 를 사용할 것이라고 확신하지는 않지만 속도 향상을 제공 할 수있는 상황은 최소한 있습니다.
C ++ 98을 표준화하는 동안 valarray는 일종의 빠른 수학적 계산을 허용하도록 설계되었습니다. 그러나 그 당시 Todd Veldhuizen은 표현 템플릿을 발명하고 blitz ++를 만들었 으며 유사한 템플릿 메타 기술이 개발되어 표준이 출시되기 전에는 valarray를 거의 쓸모 없게 만들었습니다. valarray의 최초 제안자 인 IIRC는 표준화에 반을 버렸으며, 사실 인 경우에도 도움이되지 않았습니다.
ISTR이 표준에서 제거되지 않은 주된 이유는 아무도 문제를 철저히 평가하고 제거 제안을 작성하지 않았기 때문입니다.
그러나이 모든 것이 모호하게 기억 된 소문임을 명심하십시오. 소금 한 알로 이것을 가지고 누군가가 이것을 수정하거나 확인하기를 바랍니다.
valarrays에 약간의 구문 설탕이 있다는 것을 알고 있습니다
나는 std::valarrays
구문 설탕을 많이 가지고 있다고 생각하지 않는다고 말해야합니다 . 구문은 다르지만 그 차이를 "설탕"이라고 부르지는 않습니다. API가 이상합니다. C ++ 프로그래밍 언어std::valarray
의 s 섹션 에서는이 비정상적인 API에 대해 언급하고 있으며,이 API 는 고도로 최적화 될 것으로 예상 되므로 사용 중에 발생하는 모든 오류 메시지는 직관적이지 않을 수 있습니다.std::valarray
약 1 년 전에 호기심에서 나는 std::valarray
대항했다 std::vector
. 더 이상 코드 나 정확한 결과를 얻지 못합니다 (자체를 작성하는 것이 어렵지는 않지만). GCC 내가 사용 했던 사용할 때 약간의 성능 이점을 얻을 std::valarray
내 구현 표준 편차를 계산하는 간단한 수학을 위해,하지만 (물론,과, 표준 편차가되지 복잡한 즉, 지금까지의 수학 간다). 큰 항목의 각 항목에 대한 ( 참고 , 조언을 다음 musiphil , 나는 거의 동일한에서 성능을 얻기 위해 관리했습니다 std::vector
작업이 std::valarray
s의 작업보다 캐시에서 더 잘 작동한다고 생각합니다 . vector
과 valarray
).
결국 std::vector
메모리 할당 및 임시 객체 생성과 같은 것에주의를 기울이면서 사용하기로 결정했습니다 .
모두 std::vector
와 std::valarray
연속 된 블록에 데이터를 저장합니다. 그러나 이들은 다른 패턴을 사용하여 해당 데이터에 액세스하며,보다 중요한 API는에 대한 API와 std::valarray
다른 액세스 패턴 을 권장합니다 std::vector
.
표준 편차 예제의 경우 특정 단계에서 컬렉션의 평균과 각 요소의 값과 평균의 차이를 찾아야했습니다.
에 대해 std::valarray
다음과 같은 작업을 수행했습니다.
std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> temp(mean, original_values.size());
std::valarray<double> differences_from_mean = original_values - temp;
std::slice
또는 로 더 영리했을 수도 있습니다 std::gslice
. 5 년이 지났습니다.
에 대해 std::vector
, 나는 다음 라인을 따라 무언가를했다.
std::vector<double> original_values = ... // obviously, I put something here
double mean = std::accumulate(original_values.begin(), original_values.end(), 0.0) / original_values.size();
std::vector<double> differences_from_mean;
differences_from_mean.reserve(original_values.size());
std::transform(original_values.begin(), original_values.end(), std::back_inserter(differences_from_mean), std::bind1st(std::minus<double>(), mean));
오늘 나는 확실히 다르게 쓸 것입니다. 다른 것이 없다면 C ++ 11 람다를 활용할 것입니다.
이 두 코드 스 니펫은 서로 다른 기능을 수행합니다. 예를 std::vector
들어, std::valarray
예제는 예제 와 같이 중간 컬렉션을 만들지 않습니다. 그러나, 나는 차이가 사이의 차이로 연결되어 있기 때문에 적정 그들을 비교 생각 std::vector
하고 std::valarray
.
이 답변을 쓸 때 두 개의 요소 std::valarray
( std::valarray
예 : 마지막 줄)에서 요소의 값을 빼는 것이 예제의 해당 줄 (마지막 줄이기도 함)보다 캐시 친화적이지 않을 것이라고 생각했습니다 std::vector
.
그러나 그것은
std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> differences_from_mean = original_values - mean;
는 AS 같은 일을합니까 std::vector
예를 들어, 거의 동일한 성능을 가지고있다. 결국, 당신이 선호하는 API가 문제입니다.
std::vector
보다 캐시에서 더 잘 플레이 하는 이유를 생각할 수 없다 std::valarray
. 둘 다 요소에 하나의 연속 된 메모리 블록을 할당합니다.
valarray
위 의 예제에서는 temp
valarray
객체 를 생성 할 필요가 없었지만 방금 수행했을 수 있으며 std::valarray<double> differences_from_mean = original_values - mean;
캐시 동작은 vector
예제 와 비슷해야합니다 . (그런데 mean
실제로 int
는 그렇지 않다면 double
필요할 수도 있습니다 static_cast<double>(mean)
.)
valarray
. 성능이 향상되는지 확인해야합니다. 인에 관해서 mean
는 int
: 그것은 실수였습니다. 원래 int
s 를 사용하여 예제를 작성한 mean
다음 잘림으로 인해 실제 평균과는 거리가 멀다는 것을 깨달았습니다 . 그러나 첫 편집 라운드에서 몇 가지 필요한 변경 사항을 놓쳤습니다.
valarray는 일부 FORTRAN 벡터 처리 장점이 C ++에서 문지르도록해야했습니다. 어떻게 든 필요한 컴파일러 지원이 실제로 발생하지 않았습니다.
Josuttis 책에는 valarray ( here and here ) 에 대한 흥미로운 (일부 비열한) 해설이 포함되어 있습니다 .
그러나 인텔은 최근 컴파일러 릴리스에서 valarray를 다시 방문하는 것으로 보입니다 (예 : 슬라이드 9 참조) ). 이것은 4-way SIMD SSE 명령어 세트가 8-way AVX 및 16-way Larrabee 명령어에 의해 결합 될 예정이고 이식성을 위해 다음과 같은 추상화로 코딩하는 것이 훨씬 나을 것입니다. 본질적인 것보다 valarray.
valarray에 대한 좋은 사용법을 찾았습니다. numpy 배열과 마찬가지로 valarray를 사용합니다.
auto x = linspace(0, 2 * 3.14, 100);
plot(x, sin(x) + sin(3.f * x) / 3.f + sin(5.f * x) / 5.f);
valarray로 위를 구현할 수 있습니다.
valarray<float> linspace(float start, float stop, int size)
{
valarray<float> v(size);
for(int i=0; i<size; i++) v[i] = start + i * (stop-start)/size;
return v;
}
std::valarray<float> arange(float start, float step, float stop)
{
int size = (stop - start) / step;
valarray<float> v(size);
for(int i=0; i<size; i++) v[i] = start + step * i;
return v;
}
string psstm(string command)
{//return system call output as string
string s;
char tmp[1000];
FILE* f = popen(command.c_str(), "r");
while(fgets(tmp, sizeof(tmp), f)) s += tmp;
pclose(f);
return s;
}
string plot(const valarray<float>& x, const valarray<float>& y)
{
int sz = x.size();
assert(sz == y.size());
int bytes = sz * sizeof(float) * 2;
const char* name = "plot1";
int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, bytes);
float* ptr = (float*)mmap(0, bytes, PROT_WRITE, MAP_SHARED, shm_fd, 0);
for(int i=0; i<sz; i++) {
*ptr++ = x[i];
*ptr++ = y[i];
}
string command = "python plot.py ";
string s = psstm(command + to_string(sz));
shm_unlink(name);
return s;
}
또한 파이썬 스크립트가 필요합니다.
import sys, posix_ipc, os, struct
import matplotlib.pyplot as plt
sz = int(sys.argv[1])
f = posix_ipc.SharedMemory("plot1")
x = [0] * sz
y = [0] * sz
for i in range(sz):
x[i], y[i] = struct.unpack('ff', os.read(f.fd, 8))
os.close(f.fd)
plt.plot(x, y)
plt.show()
C ++ 11 표준은 다음과 같이 말합니다.
valarray 배열 클래스는 특정 형태의 앨리어싱이없는 것으로 정의되므로 이러한 클래스의 작업을 최적화 할 수 있습니다.
C ++ 11 26.6.1-2를 참조하십시오.