Tôi thích vectơ rất nhiều. Chúng tiện lợi và nhanh chóng. Nhưng tôi biết thứ này được gọi là valarray tồn tại. Tại sao tôi sử dụng valarray thay vì vector? Tôi biết valarrays có một số đường cú pháp, nhưng ngoài ra, khi nào chúng hữu ích?
Tôi thích vectơ rất nhiều. Chúng tiện lợi và nhanh chóng. Nhưng tôi biết thứ này được gọi là valarray tồn tại. Tại sao tôi sử dụng valarray thay vì vector? Tôi biết valarrays có một số đường cú pháp, nhưng ngoài ra, khi nào chúng hữu ích?
Câu trả lời:
Các giá trị (mảng giá trị) nhằm đưa một số tốc độ của Fortran lên C ++. Bạn sẽ không tạo ra một giá trị con trỏ để trình biên dịch có thể đưa ra các giả định về mã và tối ưu hóa nó tốt hơn. (Lý do chính khiến Fortran nhanh như vậy là vì không có loại con trỏ nên không thể có bí danh con trỏ.)
Valarrays cũng có các lớp cho phép bạn cắt chúng ra một cách hợp lý dễ dàng mặc dù phần đó của tiêu chuẩn có thể sử dụng nhiều công việc hơn một chút. Thay đổi kích thước chúng là phá hoại và chúng thiếu các trình vòng lặp.
Vì vậy, nếu đó là những con số bạn đang làm việc và sự tiện lợi không phải là tất cả những giá trị sử dụng quan trọng. Nếu không, vectơ chỉ thuận tiện hơn rất nhiều.
valarray
là một đứa trẻ mồ côi được sinh ra không đúng lúc, không đúng lúc. Đó là một nỗ lực tối ưu hóa, khá cụ thể cho các máy được sử dụng cho toán học hạng nặng khi nó được viết - cụ thể là các bộ xử lý vector như Crays.
Đối với bộ xử lý vectơ, những gì bạn thường muốn làm là áp dụng một thao tác duy nhất cho toàn bộ một mảng, sau đó áp dụng thao tác tiếp theo cho toàn bộ mảng, và cứ thế cho đến khi bạn hoàn thành mọi việc bạn cần làm.
Tuy nhiên, trừ khi bạn xử lý các mảng khá nhỏ, tuy nhiên, điều đó có xu hướng hoạt động kém với bộ nhớ đệm. Trên hầu hết các máy hiện đại, những gì bạn thường thích (trong phạm vi có thể) sẽ là tải một phần của mảng, thực hiện tất cả các thao tác trên nó, sau đó chuyển sang phần tiếp theo của mảng.
valarray
cũng được cho là để loại bỏ bất kỳ khả năng răng cưa nào, điều này (ít nhất là về mặt lý thuyết) cho phép trình biên dịch cải thiện tốc độ vì việc lưu trữ các giá trị trong các thanh ghi sẽ tự do hơn. Tuy nhiên, trong thực tế, tôi không chắc chắn rằng bất kỳ triển khai thực tế nào cũng tận dụng lợi thế này ở bất kỳ mức độ đáng kể nào. Tôi nghi ngờ đây là một vấn đề rắc rối giữa gà và trứng - không có hỗ trợ trình biên dịch, nó không trở nên phổ biến và miễn là nó không phổ biến, sẽ không có ai gặp rắc rối khi làm việc với trình biên dịch của họ để hỗ trợ nó.
Ngoài ra còn có một loạt các lớp phụ trợ sử dụng với valarray. Bạn nhận được slice
, slice_array
, gslice
và gslice_array
chơi với những mảnh một valarray
, và làm cho nó hoạt động giống như một mảng đa chiều. Bạn cũng nhận đượcmask_array
thể "che dấu" một thao tác (ví dụ: thêm các mục trong x vào y, nhưng chỉ tại các vị trí mà z khác không). Để sử dụng nhiều hơn tầm thườngvalarray
, bạn phải tìm hiểu rất nhiều về các lớp phụ trợ này, một số trong đó khá phức tạp và dường như không có tài liệu nào (ít nhất là đối với tôi).
Điểm mấu chốt: trong khi nó có những khoảnh khắc rực rỡ, và có thể làm một số điều khá gọn gàng, thì cũng có một số lý do rất tốt mà nó (và gần như chắc chắn sẽ vẫn còn) tối nghĩa.
Chỉnh sửa (tám năm sau, năm 2017): Một số trước đó đã trở nên lỗi thời ở ít nhất một mức độ. Lấy một ví dụ, Intel đã triển khai một phiên bản valarray được tối ưu hóa cho trình biên dịch của họ. Nó sử dụng Nguyên tắc hiệu suất tích hợp Intel (Intel IPP) để cải thiện hiệu suất. Mặc dù cải thiện hiệu suất chính xác chắc chắn thay đổi, một thử nghiệm nhanh với mã đơn giản cho thấy tốc độ cải thiện 2: 1, so với mã giống hệt được biên dịch với triển khai "tiêu chuẩn"valarray
.
Vì vậy, trong khi tôi không hoàn toàn tin rằng các lập trình viên C ++ sẽ bắt đầu sử dụng valarray
với số lượng lớn, thì có ít nhất một số trường hợp có thể cải thiện tốc độ.
Trong quá trình chuẩn hóa C ++ 98, valarray được thiết kế để cho phép một số tính toán toán học nhanh. Tuy nhiên, khoảng thời gian đó, Todd Veldhuizen đã phát minh ra các mẫu biểu thức và tạo ra blitz ++ , và các kỹ thuật meta-meta mẫu tương tự đã được phát minh, khiến cho các giá trị khá lỗi thời trước khi tiêu chuẩn thậm chí được phát hành. IIRC, người đề xuất ban đầu của valarray đã bỏ rơi nó giữa chừng trong tiêu chuẩn hóa, điều này (nếu đúng) cũng không giúp được gì.
ISTR rằng lý do chính khiến nó không bị xóa khỏi tiêu chuẩn là vì không ai dành thời gian để đánh giá vấn đề một cách kỹ lưỡng và viết một đề xuất để loại bỏ nó.
Xin lưu ý, tuy nhiên, tất cả điều này chỉ là những tin đồn được nhớ một cách mơ hồ. Lấy cái này với một hạt muối và hy vọng ai đó sửa chữa hoặc xác nhận điều này.
Tôi biết valarrays có một số đường cú pháp
Tôi phải nói rằng tôi không nghĩ std::valarrays
có nhiều cú pháp đường. Cú pháp là khác nhau, nhưng tôi sẽ không gọi sự khác biệt là "đường." API thật kỳ lạ. Phần trên std::valarray
s trong Ngôn ngữ lập trình C ++ có đề cập đến API bất thường này và thực tế là, kể từ đóstd::valarray
chúng được dự kiến sẽ được tối ưu hóa cao, nên bất kỳ thông báo lỗi nào bạn nhận được khi sử dụng chúng có thể sẽ không trực quan.
Ra khỏi tò mò, khoảng một năm trước, tôi đọ sức std::valarray
chống lại std::vector
. Tôi không còn có mã hoặc kết quả chính xác (mặc dù không khó để tự viết). Sử dụng GCC tôi đã nhận được một chút lợi ích hiệu suất khi sử dụng std::valarray
cho toán đơn giản, nhưng không phải cho việc triển khai của tôi để tính độ lệch chuẩn (và tất nhiên, độ lệch chuẩn không quá phức tạp, theo như toán học). Tôi nghi ngờ rằng các thao tác trên mỗi mục trong một ( LƯU Ý , theo lời khuyên từ musiphil , tôi đã quản lý để có được hiệu suất gần như giống hệt từ std::vector
trò chơi lớn sẽ tốt hơn với bộ nhớ cache hơn các thao tác trên std::valarray
s. vector
và valarray
).
Cuối cùng, tôi quyết định sử dụng std::vector
trong khi chú ý đến những thứ như phân bổ bộ nhớ và tạo đối tượng tạm thời.
Cả hai std::vector
và std::valarray
lưu trữ dữ liệu trong một khối liền kề. Tuy nhiên, họ truy cập dữ liệu đó bằng các mẫu khác nhau và quan trọng hơn là API std::valarray
khuyến khích các mẫu truy cập khác với API chostd::vector
.
Đối với ví dụ độ lệch chuẩn, tại một bước cụ thể tôi cần tìm giá trị trung bình của bộ sưu tập và sự khác biệt giữa giá trị của từng phần tử và giá trị trung bình.
Đối với std::valarray
, tôi đã làm một cái gì đó như:
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;
Tôi có thể đã thông minh hơn với std::slice
hoặcstd::gslice
. Đã hơn năm năm rồi.
Đối với std::vector
, tôi đã làm một cái gì đó dọc theo dòng:
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));
Hôm nay tôi chắc chắn sẽ viết khác đi. Nếu không có gì khác, tôi sẽ tận dụng C ++ 11 lambdas.
Rõ ràng là hai đoạn mã này làm những việc khác nhau. Đối với một, std::vector
ví dụ không tạo ra một bộ sưu tập trung gian như std::valarray
ví dụ này. Tuy nhiên, tôi nghĩ thật công bằng khi so sánh chúng vì sự khác biệt gắn liền với sự khác biệt giữa std::vector
và std::valarray
.
Khi tôi viết câu trả lời này, tôi đã nghi ngờ rằng việc trừ giá trị của các phần tử từ hai std::valarray
s (dòng cuối cùng trong std::valarray
ví dụ) sẽ ít thân thiện với bộ đệm hơn so với dòng tương ứng trongstd::vector
ví dụ (cũng xảy ra là dòng cuối cùng).
Hóa ra, tuy nhiên,
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;
Làm điều tương tự như std::vector
ví dụ, và có hiệu suất gần như giống hệt nhau. Cuối cùng, câu hỏi là bạn thích API nào.
std::vector
chơi tốt hơn với bộ nhớ cache hơn a std::valarray
; cả hai đều phân bổ một khối bộ nhớ liền kề cho các phần tử của chúng.
valarray
ví dụ của bạn ở trên, bạn không phải xây dựng một temp
valarray
đối tượng, nhưng bạn có thể vừa thực hiện std::valarray<double> differences_from_mean = original_values - mean;
, và sau đó hành vi bộ đệm sẽ tương tự như đối tượng vector
. (Nhân tiện, nếu mean
thực sự int
, không double
, bạn có thể cần static_cast<double>(mean)
.)
valarray
. Tôi sẽ cần phải xem nếu điều đó cải thiện hiệu suất. Như mean
là int
: đó là một sai lầm. Ban đầu tôi đã viết ví dụ lên bằng int
s, và sau đó nhận ra rằng mean
sau đó sẽ rất xa so với ý nghĩa thực sự vì cắt ngắn. Nhưng tôi đã bỏ lỡ một vài thay đổi cần thiết trong vòng chỉnh sửa đầu tiên của mình.
valarray được cho là để cho một số lòng tốt xử lý vector FORTRAN xóa tan trên C ++. Bằng cách nào đó sự hỗ trợ trình biên dịch cần thiết không bao giờ thực sự xảy ra.
Các sách Josuttis chứa một số bình luận thú vị (hơi chê bai) về valarray ( ở đây và ở đây ).
Tuy nhiên, Intel dường như đang xem xét lại valarray trong các bản phát hành trình biên dịch gần đây của họ (ví dụ: xem slide 9 ); đây là một sự phát triển thú vị khi bộ hướng dẫn SIMD SSE 4 chiều của họ sắp được kết hợp bởi các hướng dẫn AVX 8 chiều và 16 hướng Larrabee và vì lợi ích của tính di động, việc mã hóa bằng cách trừu tượng hóa sẽ tốt hơn nhiều valarray hơn (nói) nội tại.
Tôi tìm thấy một cách sử dụng tốt cho valarray. Đó là sử dụng valarray giống như mảng numpy.
auto x = linspace(0, 2 * 3.14, 100);
plot(x, sin(x) + sin(3.f * x) / 3.f + sin(5.f * x) / 5.f);
Chúng ta có thể thực hiện ở trên với 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;
}
Ngoài ra, chúng ta cần kịch bản python.
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()
Tiêu chuẩn C ++ 11 cho biết:
Các lớp mảng valarray được định nghĩa là không có các dạng răng cưa nhất định, do đó cho phép các hoạt động trên các lớp này được tối ưu hóa.
Xem C ++ 11 26.6.1-2.
Với std::valarray
bạn có thể sử dụng ký hiệu toán học tiêu chuẩn như v1 = a*v2 + v3
ra khỏi hộp. Điều này là không thể đối với các vectơ trừ khi bạn xác định toán tử của riêng mình.
std :: valarray dành cho các tác vụ số nặng, chẳng hạn như Động lực học tính toán hoặc Động lực cấu trúc tính toán, trong đó bạn có các mảng với hàng triệu, đôi khi hàng chục triệu mục và bạn lặp lại chúng trong một vòng lặp với hàng triệu dấu thời gian. Có thể ngày nay std :: vector có hiệu suất tương đương, nhưng khoảng 15 năm trước, valarray gần như là bắt buộc nếu bạn muốn viết một bộ giải số hiệu quả.