Cách nhanh nhất để tính sin và cos với nhau là gì?


100

Tôi muốn tính cả sin và đồng sin của một giá trị với nhau (ví dụ: để tạo ma trận xoay). Tất nhiên tôi có thể tính toán chúng riêng lẻ từng cái một a = cos(x); b = sin(x);, nhưng tôi tự hỏi liệu có cách nào nhanh hơn khi cần cả hai giá trị không.

Chỉnh sửa: Để tóm tắt các câu trả lời cho đến nay:

  • Vlad nói rằng có lệnh asmFSINCOStính toán cả hai (gần như giống như một cuộc gọi đếnFSINmột mình)

  • Giống như Chi nhận thấy, việc tối ưu hóa này đôi khi đã được trình biên dịch thực hiện (khi sử dụng cờ tối ưu hóa).

  • caf đã chỉ ra rằng các chức năngsincossincosfcó thể có sẵn và có thể được gọi trực tiếp bằng cách bao gồmmath.h

  • Phương pháp tiếp cận tanascius của việc sử dụng bảng tra cứu được thảo luận gây tranh cãi. (Tuy nhiên, trên máy tính của tôi và trong một kịch bản điểm chuẩn, nó chạy nhanh hơn gấp 3 lần sosincosvới với độ chính xác gần như tương tự đối với các dấu chấm động 32 bit.)

  • Joel Goodwin đã liên kết với một cách tiếp cận thú vị của kỹ thuật xấp xỉ cực nhanh với ắc quy khá tốt (đối với tôi, điều này thậm chí còn nhanh hơn khi tra cứu bảng)


1
Xem thêm câu hỏi này về thực hiện nguồn gốc của tội lỗi / cos: stackoverflow.com/questions/1640595
Joel Goodwin

1
thử sinx ~ x-x^3/6cosx~1-x^2/4dưới dạng gần đúng nếu bạn quan tâm đến tốc độ hơn độ chính xác. Bạn có thể thêm vào các thuật ngữ trong một trong hai chuỗi khi bạn đặt nặng hơn về độ chính xác ( en.wikipedia.org/wiki/Taylor_series cuộn xuống chuỗi trig taylor.) Lưu ý rằng đây là một cách chung để ước lượng bất kỳ hàm nào bạn muốn với nthời gian khác nhau. Vì vậy, nếu bạn có một số hàm lớn hơn mà sin và cosin đó thuộc về bạn, bạn sẽ nhận được tốc độ lớn hơn nhiều nếu bạn tính gần đúng nó thay vì sin, cos độc lập.
ldog

Đây là kỹ thuật kém với độ chính xác rất kém. Xem bài đăng của Joel Goodwin. Taylor loạt đã được đăng dưới đây. Hãy đăng nó như một câu trả lời.
Danvil

1
Vâng nó phụ thuộc vào yêu cầu của bạn, nếu bạn muốn chính xác chuỗi Taylor sẽ là một tốt xấp xỉ chỉ khi bạn cần giá trị của xgần một lúc nào đó x_0, sau đó mở rộng chuỗi Taylor của bạn xung quanh x_0thay vì 0. Điều này sẽ cung cấp cho bạn độ chính xác tuyệt vời gần x_0nhưng xa hơn bạn kết quả càng tồi tệ. Bạn có thể nghĩ rằng độ chính xác tệ gây ra khi bạn nhìn vào asnwer đã cho và thử nó cho các giá trị khác xa 0. Câu trả lời đó là với sin, cos mở rộng khoảng 0.
ldog

Câu trả lời:


52

Bộ vi xử lý Intel / AMD hiện đại có hướng dẫn FSINCOStính toán hàm sin và hàm cosin đồng thời. Nếu bạn cần tối ưu hóa mạnh mẽ, có lẽ bạn nên sử dụng nó.

Đây là một ví dụ nhỏ: http://home.broadpark.no/~alein/fsincos.html

Đây là một ví dụ khác (dành cho MSVC): http://www.codeguru.com/forum/showthread.php?t=328669

Đây là một ví dụ khác (với gcc): http://www.allegro.cc/forums/thread/588470

Hy vọng một trong số họ giúp đỡ. (Xin lỗi, tôi đã không sử dụng hướng dẫn này.)

Vì chúng được hỗ trợ ở cấp bộ xử lý, tôi hy vọng chúng sẽ nhanh hơn nhiều so với tra cứu bảng.

Chỉnh sửa:
Wikipedia gợi ý rằng FSINCOSđã được thêm vào 387 bộ xử lý, vì vậy bạn khó có thể tìm thấy bộ xử lý không hỗ trợ nó.

Chỉnh sửa:
Tài liệu của IntelFSINCOS chỉ ra rằng tốc độ này chỉ chậm hơn khoảng 5 lần so với FDIV(tức là phép chia dấu phẩy động).

Chỉnh sửa:
Xin lưu ý rằng không phải tất cả các trình biên dịch hiện đại đều tối ưu hóa phép tính sin và cosine thành một lệnh gọi tới FSINCOS. Đặc biệt, VS 2008 của tôi đã không làm theo cách đó.

Chỉnh sửa:
Liên kết ví dụ đầu tiên đã chết, nhưng vẫn còn một phiên bản tại Wayback Machine .


1
@phkahler: Điều đó sẽ rất tuyệt. Không biết liệu tối ưu hóa như vậy có được sử dụng bởi các trình biên dịch hiện đại hay không.
Vlad

12
Các fsincoshướng dẫn là không "khá nhanh". Sách hướng dẫn tối ưu hóa của chính Intel trích dẫn nó yêu cầu từ 119 đến 250 chu kỳ trên các kiến ​​trúc vi mô gần đây. Thư viện toán học của Intel (được phân phối với ICC), để so sánh, có thể tính toán riêng biệtsincostrong ít hơn 100 chu kỳ, sử dụng triển khai phần mềm sử dụng SSE thay vì đơn vị x87. Việc triển khai phần mềm tương tự tính toán cả hai đồng thời vẫn có thể nhanh hơn.
Stephen Canon

2
@Vlad: Các thư viện toán học ICC không phải là mã nguồn mở và tôi không có giấy phép để phân phối lại chúng, vì vậy tôi không thể đăng tập hợp. Tuy nhiên, tôi có thể nói với bạn rằng không có sintính toán tích hợp nào để họ tận dụng; họ sử dụng các hướng dẫn SSE giống như những người khác. Theo nhận xét thứ hai của bạn, tốc độ liên quan đến fdivlà phi vật chất; Nếu có hai cách để làm một việc gì đó và một cách nhanh gấp đôi cách còn lại, thì sẽ không hợp lý khi gọi cách chậm hơn là "nhanh", bất kể nó mất bao lâu so với một số nhiệm vụ hoàn toàn không liên quan.
Stephen Canon

1
sinChức năng phần mềm trong thư viện của họ mang lại độ chính xác kép đầy đủ. Lệnh này fsincosmang lại độ chính xác cao hơn một chút (mở rộng gấp đôi), nhưng độ chính xác cao hơn đó bị loại bỏ trong hầu hết các chương trình gọi sinhàm, vì kết quả của nó thường được làm tròn thành độ chính xác gấp đôi bằng các phép toán số học sau này hoặc lưu vào bộ nhớ. Trong hầu hết các tình huống, chúng mang lại độ chính xác như nhau để sử dụng thực tế.
Stephen Canon

4
Cũng lưu ý rằng đó fsincoskhông phải là một triển khai hoàn chỉnh của riêng nó; bạn cần một bước giảm phạm vi bổ sung để đưa đối số vào phạm vi đầu vào hợp lệ cho fsincoslệnh. Thư viện sinvà các coschức năng bao gồm sự giảm thiểu này cũng như tính toán cốt lõi, vì vậy chúng thậm chí còn nhanh hơn (bằng cách so sánh) so với thời gian chu kỳ mà tôi đã liệt kê có thể chỉ ra.
Stephen Canon

39

Bộ xử lý x86 hiện đại có lệnh fsincos sẽ thực hiện chính xác những gì bạn đang yêu cầu - tính toán sin và cos cùng một lúc. Một trình biên dịch tối ưu hóa tốt sẽ phát hiện mã tính toán sin và cos cho cùng một giá trị và sử dụng lệnh fsincos để thực hiện điều này.

Phải mất một số cuộn cờ trình biên dịch để điều này hoạt động, nhưng:

$ gcc --version
i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5488)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cat main.c
#include <math.h> 

struct Sin_cos {double sin; double cos;};

struct Sin_cos fsincos(double val) {
  struct Sin_cos r;
  r.sin = sin(val);
  r.cos = cos(val);
  return r;
}

$ gcc -c -S -O3 -ffast-math -mfpmath=387 main.c -o main.s

$ cat main.s
    .text
    .align 4,0x90
.globl _fsincos
_fsincos:
    pushl   %ebp
    movl    %esp, %ebp
    fldl    12(%ebp)
    fsincos
    movl    8(%ebp), %eax
    fstpl   8(%eax)
    fstpl   (%eax)
    leave
    ret $4
    .subsections_via_symbols

Tada, nó sử dụng lệnh fsincos!


Điều này thật tuyệt! Bạn có thể giải thích -mfpmath = 387 đang làm gì không? Và nó cũng hoạt động với MSVC?
Danvil

1
Lưu ý rằng -ffast-math-mfpmathdẫn đến kết quả khác nhau trong một số trường hợp.
Debilski

3
mfpmath = 387 sẽ buộc gcc sử dụng lệnh x87 thay vì lệnh SSE. Tôi nghi ngờ MSVC có các cờ và tối ưu hóa tương tự, nhưng tôi không có sẵn MSVC để chắc chắn. Tuy nhiên, việc sử dụng hướng dẫn x87 có thể sẽ gây tổn hại đến hiệu suất trong mã khác, bạn cũng nên xem câu trả lời khác của tôi, để sử dụng MKL của Intel.
Chi

Gcc 3.4.4 cũ của tôi từ cygwin tạo ra 2 cuộc gọi riêng biệt đến fsinfcos. :-(
Vlad

Đã thử với Visual Studio 2008 với tối ưu hóa cao nhất được bật. Nó gọi 2 hàm thư viện __CIsin__CIcos.
Vlad

13

Khi bạn cần hiệu suất, bạn có thể sử dụng bảng sin / cos được tính toán trước (một bảng sẽ làm được, được lưu trữ dưới dạng Từ điển). Vâng, nó phụ thuộc vào độ chính xác bạn cần (có thể bảng sẽ lớn), nhưng nó phải thực sự nhanh.


Sau đó, giá trị đầu vào cần được ánh xạ đến [0,2 * pi] (hoặc nhỏ hơn với các kiểm tra bổ sung) và lệnh gọi fmod này sẽ làm giảm hiệu suất. Trong quá trình triển khai (có thể là dưới mức tối ưu), tôi không thể đạt được hiệu suất với bảng tra cứu. Bạn có lời khuyên nào ở đây không?
Danvil

11
Một bảng được tính toán trước gần như chắc chắn sẽ chậm hơn so với việc chỉ gọi sinvì bảng được tính toán trước sẽ làm thùng rác bộ nhớ cache.
Andreas Brinck

1
Nó phụ thuộc vào độ lớn của bàn. Một bảng 256 mục nhập thường đủ chính xác và chỉ sử dụng 1Kb ... nếu bạn sử dụng nó nhiều, nó sẽ không bị kẹt trong bộ nhớ cache mà không ảnh hưởng xấu đến hiệu suất còn lại của ứng dụng?
Mr. Boy

@Danvil: Đây là một ví dụ về bảng tra cứu sin en.wikipedia.org/wiki/Lookup_table#Computing_sines . Tuy nhiên, nó giả định rằng bạn cũng đã ánh xạ đầu vào của mình thành [0; 2pi].
tanascius

@AndreasBrinck Tôi sẽ không đi xa như vậy. Nó phụ thuộc (TM). Bộ nhớ đệm hiện đại rất lớn và bảng tra cứu nhỏ. Khá thường xuyên nếu bạn cẩn thận một chút trong việc bố trí bộ nhớ, bảng tra cứu của bạn sẽ không tạo ra bất kỳ sự khác biệt nào đối với việc sử dụng bộ nhớ cache của phần còn lại của tính toán của bạn. Thực tế là bảng tra cứu nằm trong bộ nhớ cache là một trong những lý do khiến nó rất nhanh. Ngay cả trong Java, nơi rất khó để kiểm soát bố cục mem một cách chính xác, tôi đã có những chiến thắng hiệu suất lớn với các bảng tra cứu.
Jarrod Smith

13

Về mặt kỹ thuật, bạn sẽ đạt được điều này bằng cách sử dụng số phức và Công thức của Euler . Do đó, một cái gì đó như (C ++)

complex<double> res = exp(complex<double>(0, x));
// or equivalent
complex<double> res = polar<double>(1, x);
double sin_x = res.imag();
double cos_x = res.real();

sẽ cung cấp cho bạn sin và cosine trong một bước. Làm thế nào điều này được thực hiện trong nội bộ là một câu hỏi về trình biên dịch và thư viện đang được sử dụng. Có thể (và có thể) sẽ mất nhiều thời gian hơn để làm theo cách này (chỉ vì Công thức của Euler chủ yếu được sử dụng để tính toán phức hợp expbằng cách sử dụng sincos- chứ không phải theo cách khác) nhưng có thể có một số tối ưu hóa lý thuyết.


Biên tập

Các tiêu đề trong <complex>GNU C ++ 4.2 đang sử dụng các tính toán rõ ràng về sincosbên trong polar, vì vậy nó không quá tốt để tối ưu ở đó trừ khi trình biên dịch thực hiện một số phép thuật (xem -ffast-math-mfpmathchuyển đổi như được viết trong câu trả lời của Chi ).


xin lỗi, nhưng Công thức của Euler không thực sự cho bạn biết cách tính toán thứ gì đó, nó chỉ là một danh tính (mặc dù rất hữu ích) liên quan đến cấp số nhân phức tạp với các hàm lượng giác thực. Có những lợi ích khi tính sin và cosine cùng nhau, nhưng chúng liên quan đến các biểu thức con chung và câu trả lời của bạn không thảo luận về điều này.
Jason S

12

Bạn có thể tính toán một trong hai và sau đó sử dụng danh tính:

cos (x) 2 = 1 - sin (x) 2

nhưng như @tanascius nói, một bảng được tính toán trước là cách để đi.


8
Và lưu ý rằng sử dụng phương pháp này liên quan đến tính toán lũy thừa và căn bậc hai, vì vậy nếu hiệu suất là quan trọng, hãy đảm bảo xác minh rằng điều này thực sự nhanh hơn so với tính toán trực tiếp hàm trig khác.
Tyler McHenry

4
sqrt()thường được tối ưu hóa trong phần cứng, vì vậy nó rất có thể nhanh hơn sau đó sin()hoặc cos(). Sức mạnh chỉ là tự nhân lên, vì vậy đừng sử dụng pow(). Có một số thủ thuật để lấy căn bậc hai chính xác hợp lý rất nhanh chóng mà không cần hỗ trợ phần cứng. Cuối cùng, hãy chắc chắn hồ sơ trước khi thực hiện bất kỳ điều nào.
deft_code

12
Lưu ý rằng √ (1 - cos ^ 2 x) là kém chính xác hơn so với tính toán sin x trực tiếp, đặc biệt là khi x ~ 0.
kennytm

1
Đối với x nhỏ, chuỗi Taylor cho y = sqrt (1-x * x) là rất tốt. Bạn có thể có được độ chính xác tốt với 3 số hạng đầu tiên và nó chỉ yêu cầu một vài phép nhân và một lần dịch chuyển. Tôi đã sử dụng nó trong mã điểm cố định.
phkahler

1
@phkahler: Chuỗi Taylor của bạn không áp dụng vì khi x ~ 0, cos x ~ 1
kennytm

10

Nếu bạn sử dụng thư viện GNU C, thì bạn có thể làm:

#define _GNU_SOURCE
#include <math.h>

và bạn sẽ nhận được tờ khai của sincos(), sincosf()sincosl()chức năng mà tính cả hai giá trị với nhau - có lẽ trong cách nhanh nhất cho kiến trúc mục tiêu của bạn.


8

Có một nội dung rất thú vị trên trang diễn đàn này, tập trung vào việc tìm các giá trị gần đúng nhanh chóng: http://www.devmaster.net/forums/showthread.php?t=5784

Tuyên bố từ chối trách nhiệm: Bản thân tôi chưa sử dụng bất kỳ thứ nào trong số này.

Cập nhật ngày 22 tháng 2 năm 2018: Wayback Machine là cách duy nhất để truy cập trang gốc ngay bây giờ: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast-and-accurate- sin-cosine


Tôi cũng đã thử cái này, và nó đã cho tôi hiệu suất khá tốt. Nhưng sin và cos được tính toán một cách độc lập.
Danvil

Cảm giác của tôi là phép tính sin / cosine này sẽ nhanh hơn việc tính sin và sử dụng phép xấp xỉ căn bậc hai để lấy cosine, nhưng một bài kiểm tra sẽ xác minh điều đó. Mối quan hệ chính giữa sin và cosine là một trong những giai đoạn; có thể viết mã để bạn có thể sử dụng lại các giá trị sin mà bạn tính toán cho các lệnh gọi cosin dịch pha bằng cách tính đến điều này không? (Điều này có thể là một khoảng thời gian, nhưng phải hỏi)
Joel Goodwin

Không trực tiếp (mặc dù câu hỏi yêu cầu chính xác điều này). Tôi cần tội lỗi và cos của một giá trị x và không có cách nào để biết nếu ở một nơi nào khác mà tôi tình cờ tính x + pi / 2 ...
Danvil

Tôi đã sử dụng nó trong trò chơi của mình để vẽ một vòng tròn các hạt. Vì nó chỉ là một hiệu ứng hình ảnh, nên kết quả là đủ gần và hiệu suất thực sự ấn tượng.
Maxim Kamalov

Tôi không thấy ấn tượng gì; Các phép gần đúng Chebyshev thường cung cấp cho bạn độ chính xác nhất cho một hiệu suất nhất định.
Jason S

7

Nhiều thư viện toán học C, như caf đã chỉ ra, đã có sincos (). Ngoại lệ đáng chú ý là MSVC.

  • Sun đã có sincos () ít nhất là từ năm 1987 (hai mươi ba năm; tôi có một trang bản cứng)
  • HPUX 11 đã có nó vào năm 1997 (nhưng không có trong HPUX 10.20)
  • Đã thêm vào glibc trong phiên bản 2.1 (tháng 2 năm 1999)
  • Trở thành phiên bản cài sẵn trong gcc 3.4 (2004), __builtin_sincos ().

Và liên quan đến việc tra cứu, Eric S. Raymond trong Nghệ thuật lập trình Unix (2004) (Chương 12) nói rõ ràng đây là một Ý tưởng Xấu (tại thời điểm hiện tại):

"Một ví dụ khác là tính toán trước các bảng nhỏ - ví dụ: bảng sin (x) theo độ để tối ưu hóa các phép quay trong công cụ đồ họa 3D sẽ chiếm 365 × 4 byte trên một máy hiện đại. Trước khi bộ xử lý có đủ nhanh hơn bộ nhớ để yêu cầu bộ nhớ đệm , đây là một sự tối ưu hóa tốc độ rõ ràng. Ngày nay, việc tính toán lại mỗi lần có thể nhanh hơn thay vì phải trả phần trăm bộ nhớ cache bổ sung do bảng gây ra.

"Nhưng trong tương lai, điều này có thể quay lại khi bộ nhớ đệm phát triển lớn hơn. Nói chung, nhiều tối ưu hóa chỉ là tạm thời và có thể dễ dàng biến thành bi quan khi tỷ lệ chi phí thay đổi. Cách duy nhất để biết là đo lường và xem." (từ Nghệ thuật lập trình Unix )

Nhưng, đánh giá từ cuộc thảo luận trên, không phải ai cũng đồng ý.


10
"365 x 4 byte". Bạn cần tính đến năm nhuận, do đó thực tế phải là 365,25 x 4 byte. Hoặc có thể ý của ông là sử dụng số độ trong một vòng tròn thay vì số ngày trong một năm trái đất.
Ponkadoodle

@Wallacoloo: Quan sát tốt đẹp. Tôi nhớ nó. Nhưng lỗi là ở bản gốc .
Joseph Quinsey

CƯỜI LỚN. Thêm vào đó, anh ấy bỏ qua thực tế rằng trong nhiều trò chơi máy tính của khu vực đó, bạn sẽ chỉ cần một số góc hữu hạn. Không có bộ nhớ cache nào bị bỏ lỡ sau đó, nếu bạn biết các góc độ có thể. Tôi sẽ sử dụng các bảng chính xác trong trường hợp này và đưa ra fsincos(hướng dẫn CPU!) Cho các bảng khác. Nó thường nhanh như nội suy sin và cos từ một bảng lớn.
Erich Schubert

5

Tôi không tin rằng bảng tra cứu nhất thiết phải là một ý tưởng tốt cho vấn đề này. Trừ khi yêu cầu về độ chính xác của bạn rất thấp, bảng cần phải rất lớn. Và các CPU hiện đại có thể thực hiện nhiều phép tính trong khi một giá trị được tìm nạp từ bộ nhớ chính. Đây không phải là một trong những câu hỏi có thể được trả lời đúng bằng lập luận (thậm chí không phải của tôi), kiểm tra và đo lường và xem xét dữ liệu.

Nhưng tôi muốn xem xét các triển khai nhanh chóng của SinCos mà bạn tìm thấy trong các thư viện như ACML của AMD và MKL của Intel.


3

Nếu bạn sẵn sàng sử dụng một sản phẩm thương mại và đang tính toán một số phép tính sin / cos cùng một lúc (để bạn có thể sử dụng các hàm vectơ), bạn nên xem Thư viện Hạt nhân Toán học của Intel.

Nó có một hàm sincos

Theo tài liệu đó, nó đạt trung bình 13,08 xung nhịp / phần tử trên bộ đôi lõi 2 ở chế độ chính xác cao, mà tôi nghĩ sẽ nhanh hơn cả fsincos.


1
Tương tự, trên OSX, người ta có thể sử dụng vvsincoshoặc vvsincosftừ Accelerate.framework. Tôi tin rằng AMD cũng có các chức năng tương tự trong thư viện vectơ của họ.
Stephen Canon


2

Khi hiệu suất là rất quan trọng đối với loại điều này, không có gì lạ khi giới thiệu một bảng tra cứu.


2

Đối với một cách tiếp cận sáng tạo, làm thế nào về việc mở rộng chuỗi Taylor? Vì chúng có các thuật ngữ tương tự nhau, bạn có thể làm điều gì đó giống như giả sau:

numerator = x
denominator = 1
sine = x
cosine = 1
op = -1
fact = 1

while (not enough precision) {
    fact++
    denominator *= fact
    numerator *= x

    cosine += op * numerator / denominator

    fact++
    denominator *= fact
    numerator *= x

    sine += op * numerator / denominator

    op *= -1
}

Điều này có nghĩa là bạn làm điều gì đó như sau: bắt đầu tại x và 1 cho sin và cosine, theo mô hình - trừ x ^ 2/2! từ cosine, trừ x ^ 3/3! từ sin, thêm x ^ 4/4! thành cosin, thêm x ^ 5/5! đến sin ...

Tôi không biết liệu điều này có hiệu quả hay không. Nếu bạn cần độ chính xác ít hơn so với hàm sin () và cos () được tích hợp sẵn cho bạn, nó có thể là một tùy chọn.


Trên thực tế, hệ số mở rộng i-the sin là x / i nhân với hệ số mở rộng i-the cosine. Nhưng tôi sẽ nghi ngờ rằng việc sử dụng các chuỗi Taylor thực sự là nhanh ...
Danvil

1
Chebyshev tốt hơn nhiều so với Taylor về tính gần đúng hàm đa thức. Không sử dụng xấp xỉ Taylor.
Timmmm

Có một loạt các pas giả số ở đây; tử số và mẫu số đều nhanh chóng trở nên lớn và dẫn đến lỗi dấu phẩy động. Chưa kể làm thế nào để bạn quyết định "không đủ chính xác" là gì và làm thế nào để tính toán nó? Xấp xỉ Taylor là tốt trong vùng lân cận xung quanh một điểm duy nhất; từ thời điểm đó, chúng nhanh chóng trở nên không chính xác và yêu cầu một số lượng lớn các số hạng, đó là lý do tại sao gợi ý của Timmmm về phép gần đúng Chebyshev (tạo ra các phép gần đúng tốt trong một khoảng nhất định) là một gợi ý tốt.
Jason S

2

Có một giải pháp hay trong thư viện CEPHES có thể khá nhanh và bạn có thể thêm / bớt độ chính xác khá linh hoạt với thời gian CPU nhiều hơn / ít hơn một chút.

Hãy nhớ rằng cos (x) và sin (x) là phần thực và phần ảo của exp (ix). Vì vậy, chúng tôi muốn tính toán exp (ix) để có được cả hai. Chúng tôi tính toán trước exp (iy) cho một số giá trị rời rạc của y trong khoảng từ 0 đến 2pi. Ta chuyển x vào khoảng [0, 2pi). Sau đó, chúng ta chọn y gần nhất với x và viết
exp (ix) = exp (iy + (ix-iy)) = exp (iy) exp (i (xy)).

Chúng tôi nhận được exp (iy) từ bảng tra cứu. Và kể từ khi | xy | nhỏ (nhiều nhất là một nửa khoảng cách giữa các giá trị y), chuỗi Taylor sẽ hội tụ độc đáo chỉ trong một vài số hạng, vì vậy chúng tôi sử dụng nó cho exp (i (xy)). Và sau đó chúng ta chỉ cần một phép nhân phức tạp để có được exp (ix).

Một thuộc tính tốt đẹp khác của nó là bạn có thể vector hóa nó bằng SSE.


2

Bạn có thể muốn xem tại http://gruntthepeon.free.fr/ssemath/ , nơi cung cấp triển khai vectơ SSE lấy cảm hứng từ thư viện CEPHES. Nó có độ chính xác tốt (độ lệch tối đa từ sin / cos theo thứ tự 5e-8) và tốc độ (hơi tốt hơn fsincos trên cơ sở một cuộc gọi và người chiến thắng rõ ràng trên nhiều giá trị).




0

Bạn đã nghĩ đến việc khai báo các bảng tra cứu cho hai hàm chưa? Bạn vẫn phải "tính toán" sin (x) và cos (x), nhưng nó sẽ nhanh hơn một cách quyết định, nếu bạn không cần độ chính xác cao.


0

Trình biên dịch MSVC có thể sử dụng các hàm SSE2 (nội bộ)

 ___libm_sse2_sincos_ (for x86)
 __libm_sse2_sincos_  (for x64)

trong các bản dựng được tối ưu hóa nếu cờ trình biên dịch thích hợp được chỉ định (tối thiểu / O2 / vòm: SSE2 / fp: nhanh). Tên của các hàm này dường như ngụ ý rằng chúng không tính sin và cos riêng biệt, mà cả hai đều "trong một bước".

Ví dụ:

void sincos(double const x, double & s, double & c)
{
  s = std::sin(x);
  c = std::cos(x);
}

Assembly (cho x86) với / fp: fast:

movsd   xmm0, QWORD PTR _x$[esp-4]
call    ___libm_sse2_sincos_
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
mov     eax, DWORD PTR _c$[esp-4]
shufpd  xmm0, xmm0, 1
movsd   QWORD PTR [eax], xmm0
ret     0

Assembly (cho x86) không có / fp: nhanh nhưng có / fp: chính xác thay thế (là mặc định) gọi sin và cos riêng biệt:

movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_sin_precise
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_cos_precise
mov     eax, DWORD PTR _c$[esp-4]
movsd   QWORD PTR [eax], xmm0
ret     0

Vì vậy / fp: nhanh là bắt buộc để tối ưu hóa sincos.

Nhưng xin lưu ý rằng

___libm_sse2_sincos_

có thể không chính xác bằng

__libm_sse2_sin_precise
__libm_sse2_cos_precise

do thiếu "precision" ở cuối tên của nó.

Trên hệ thống cũ hơn "một chút" của tôi (Intel Core 2 Duo E6750) với trình biên dịch MSVC 2019 mới nhất và các tối ưu hóa phù hợp, điểm chuẩn của tôi cho thấy lệnh gọi sincos nhanh hơn khoảng 2,4 lần so với lệnh gọi sin và cos riêng biệt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.