2013-05-29 36 views
10

Tôi đang tìm các vấn đề về mô hình/lỗi dấu phẩy động khá khó hiểu. Đó là một khu vực tôi không quen thuộc và tôi không phải là một lập trình viên C/asm cấp thấp, vì vậy tôi sẽ đánh giá cao một chút lời khuyên.Xử lý ngoại lệ Floating-Point trong C++

Tôi có ứng dụng C++ được xây dựng với VS2012 (VC11) mà tôi đã định cấu hình để ném ngoại lệ dấu chấm động (hoặc chính xác hơn, để cho phép thời gian chạy và/hoặc phần cứng C++ ném các ngoại lệ fp) - và ném khá nhiều trong số chúng trong bản phát hành (được tối ưu hóa), nhưng không phải trong bản dựng gỡ lỗi. Tôi cho rằng điều này là do tối ưu hóa và có lẽ là mô hình dấu chấm động (mặc dù trình biên dịch/fp: chuyển đổi chính xác được đặt cho cả bản phát hành và bản dựng gỡ lỗi).

Câu hỏi đầu tiên của tôi liên quan đến quản lý gỡ lỗi ứng dụng. Tôi muốn kiểm soát nơi mà các ngoại lệ fp được ném ra và chúng được "che khuất" ở đâu. Điều này là cần thiết vì tôi đang gỡ lỗi bản phát hành (tối ưu hóa) (là nơi xảy ra các ngoại lệ fp) - và tôi muốn vô hiệu hóa các ngoại lệ fp trong các chức năng nhất định mà tôi đã phát hiện vấn đề, vì vậy tôi có thể tìm ra các vấn đề FP mới. Nhưng tôi bối rối bởi sự khác biệt giữa việc sử dụng _controlfp_s để làm điều này (mà hoạt động tốt) và trình biên dịch (và #pragma float_control) chuyển đổi "/ fp: ngoại trừ" (mà dường như không có hiệu lực). Sự khác biệt giữa hai cơ chế này là gì? Họ có phải có tác dụng tương tự đối với trường hợp ngoại lệ fp không?

Thứ hai, tôi nhận được một số ngoại lệ "Kiểm tra ngăn xếp dấu chấm động" - bao gồm một ngoại lệ có vẻ như được gửi trong cuộc gọi tới dll GDI +. Tìm kiếm trên web, vài đề cập về ngoại lệ này dường như chỉ ra rằng đó là do lỗi trình biên dịch. Đây có phải là trường hợp này không? Nếu vậy, làm thế nào tôi nên làm việc vòng này? Tốt nhất là vô hiệu hóa tối ưu hóa trình biên dịch cho các chức năng vấn đề, hoặc để vô hiệu hóa các ngoại lệ fp chỉ cho các vùng mã có vấn đề nếu không xuất hiện bất kỳ giá trị dấu phẩy động nào bị trả về? Ví dụ, trong cuộc gọi GDI + (đến GraphicsPath :: GetPointCount) mà ném ngoại lệ này, giá trị số nguyên trả về thực tế có vẻ đúng. Hiện tại tôi đang sử dụng _controlfp_s để vô hiệu hóa các ngoại lệ fp ngay trước cuộc gọi GDI + - và sau đó sử dụng lại lần nữa để bật lại ngoại lệ ngay sau cuộc gọi.

Cuối cùng, ứng dụng của tôi thực hiện nhiều tính toán dấu phẩy động và cần phải mạnh mẽ và đáng tin cậy, nhưng không nhất thiết phải cực kỳ chính xác. Bản chất của ứng dụng là các giá trị dấu phẩy động thường chỉ ra các xác suất, do đó vốn có phần không chính xác. Tuy nhiên, tôi muốn bẫy bất kỳ lỗi logic thuần túy nào, chẳng hạn như chia cho số không. Mô hình fp tốt nhất cho điều này là gì? Hiện nay tôi đang:

  • bẫy tất cả các trường hợp ngoại lệ fp (tức EM_OVERFLOW | EM_UNDERFLOW | EM_ZERODIVIDE | EM_DENORMAL | EM_INVALID) sử dụng _controlfp_s và một handler SIGFPE Signal,
  • đã kích hoạt denormals-là-zero (DAZ) và tuôn ra -to-zero (FTZ) (tức là _MM_SET_FLUSH_ZERO_MODE (_MM_DENORMALS_ZERO_ON)) và
  • Tôi đang sử dụng cài đặt trình biên dịch VC11 mặc định/fp: chính xác với/fp: ngoại trừ không được chỉ định.

Đây có phải là mô hình tốt nhất không?

Cảm ơn và kính trọng!

+0

== "Tôi muốn kiểm soát nơi ngoại lệ fp được ném và vị trí chúng bị" che khuất "." == Ngoại lệ không bao giờ được che mặt nạ. Nếu chúng được đeo mặt nạ thì bạn không nên ném ngoại lệ ngay từ đầu. Nếu bằng cách che mặt, bạn có nghĩa là xử lý các ngoại lệ sau đó, nó phải giống nhau cả trong gỡ lỗi và phát hành. – Ram

+0

Xin chào Ram. Điều này chủ yếu để gỡ lỗi ứng dụng của tôi - tôi muốn vô hiệu hóa các ngoại lệ fp trong các chức năng nhất định mà tôi đã phát hiện sự cố, vì vậy tôi có thể tìm ra các vấn đề mới. Tôi cũng có vấn đề với trường hợp ngoại lệ fp được thực hiện bởi cuộc gọi GDI +. Hiện tại tôi sử dụng _controlfp_s để vô hiệu hóa các ngoại lệ fp ngay trước cuộc gọi - và sau đó sử dụng nó để bật lại ngoại lệ ngay sau đó. Và tôi không chắc chắn tất cả các ngoại lệ fp là nhất thiết có liên quan (ví dụ EM_DENORMAL) - mà tôi giả định là lý do tại sao VC (và các trình biên dịch khác?) Tắt chúng theo mặc định. Tôi có thể làm với lời khuyên về điều này - do đó câu hỏi cuối cùng ở trên. – Jools99

+3

@Ram: Những ngoại lệ này không được nêu ra trong phần mềm, chúng được tạo ra do lỗi FPU. Masking xác định liệu lỗi có trở thành ngoại lệ hay không. Đừng nghĩ về xử lý ngoại lệ C++, 'try' /' catch', hoặc stack unwinding. Đây cũng là ngoại lệ nhưng hành vi khác với ngoại lệ C++. –

Trả lời

0

Tôi đã đấu tranh để đạt được một số thông tin về xử lý ngoại lệ điểm nổi trên Linux và tôi có thể cho bạn biết những gì tôi đã học: Có một vài cách để tạo điều kiện cho các cơ chế ngoại lệ:

  1. fesetenv (FE_NOMASK_ENV); cho phép tất cả ngoại lệ
  2. feenableexcept (FE_ALL_EXCEPT);
fpu_control_t fw; 
_FPU_GETCW(fw); 
fw |=FE_ALL_EXCEPT; 
_FPU_SETCW(fw); 

4.

> fenv_t envp; include bits/fenv.h 
> fegetenv(&envp);  
envp.__control_word |= ~_FPU_MASK_OM; 
> fesetenv(&envp); 

5.

> fpu_control_t cw; 
> __asm__ ("fnstcw %0" : "=m" (*&cw));get config word 
>cw |= ~FE_UNDERFLOW; 
> __asm__ ("fldcw %0" : : "m" (*&cw));write config word 

6.C++ chế độ: std :: feclearexcept (FE_ALL_EXCEPT);

Có một số liên kết hữu ích: http://frs.web.cern.ch/frs/Source/MAC_headers/fpu_control.h http://en.cppreference.com/w/cpp/numeric/fenv/fetestexcept http://technopark02.blogspot.ro/2005/10/handling-sigfpe.html

1

tôi không thể giúp được gì nhiều với hai câu hỏi đầu tiên, nhưng tôi có kinh nghiệm và góp ý đối với câu hỏi về mặt nạ ngoại lệ FPU.

tôi đã tìm thấy các chức năng

_statusfp() (x64 and Win32) 
_statusfp2() (Win32 only) 
_fpreset() 
_controlfp_s() 
_clearfp() 
_matherr() 

hữu ích khi gỡ lỗi ngoại lệ FPU và trong việc cung cấp một sản phẩm ổn định và nhanh chóng.

Khi gỡ lỗi, tôi chọn lọc các ngoại lệ để giúp cô lập dòng mã nơi ngoại lệ fpu được tạo trong một phép tính, nơi tôi không thể tránh gọi mã khác không thể đoán trước được ngoại lệ fpu (như phân chia .NET JIT bằng số không).

Trong sản phẩm được phát hành, tôi sử dụng chúng để cung cấp một chương trình ổn định có thể chịu đựng được các ngoại lệ dấu chấm động nghiêm trọng, phát hiện khi chúng xuất hiện và khôi phục một cách duyên dáng.

Tôi mặt nạ tất cả các ngoại lệ FPU khi tôi phải gọi mã không thể thay đổi, không có ngoại lệ đáng tin cậy, và đôi khi tạo ra ngoại lệ FPU.

Ví dụ:

#define BAD_FPU_EX (_EM_OVERFLOW | _EM_ZERODIVIDE | _EM_INVALID) 
#define COMMON_FPU_EX (_EM_INEXACT | _EM_UNDERFLOW | _EM_DENORMAL) 
#define ALL_FPU_EX (BAD_FPU_EX | COMMON_FPU_EX) 

đang phát hành: Mã

_fpreset(); 
Use _controlfp_s() to mask ALL_FPU_EX 
_clearfp(); 
... calculation 
unsigned int bad_fpu_ex = (BAD_FPU_EX & _statusfp()); 
_clearfp(); // to prevent reacting to existing status flags again 
if (0 != bad_fpu_ex) 
{ 
    ... use fallback calculation 
    ... discard result and return error code 
    ... throw exception with useful information 
} 

gỡ lỗi:

_fpreset(); 
_clearfp(); 
Use _controlfp_s() to mask COMMON_FPU_EX and unmask BAD_FPU_EX 
... calculation 
    "crash" in debugger on the line of code that is generating the "bad" exception. 

Tùy thuộc vào com của bạn tùy chọn piler, xây dựng phát hành có thể được sử dụng các cuộc gọi nội tại để FPU ops và gỡ lỗi xây dựng có thể gọi chức năng thư viện toán học. Hai phương thức này có thể có hành vi xử lý lỗi khác nhau đáng kể đối với các hoạt động không hợp lệ như sqrt (-1.0).

Sử dụng tệp thi hành được xây dựng với VS2010 trên Windows 7 64 bit, tôi đã tạo các giá trị số học chính xác gấp đôi hơi khác nhau khi sử dụng mã giống nhau trên nền tảng Win32 và x64. Ngay cả khi sử dụng bản dựng gỡ lỗi không được tối ưu hóa với/fp :: chính xác, điều khiển chính xác fpu được đặt rõ ràng thành _PC_53 và điều khiển làm tròn fpu được đặt rõ ràng thành _RC_NEAR. Tôi đã phải điều chỉnh một số thử nghiệm hồi quy so sánh các giá trị chính xác kép để đưa nền tảng vào tài khoản. Tôi không biết nếu điều này vẫn còn là một vấn đề với VS2012, nhưng đứng đầu.

4

Hầu hết các thông tin sau đây đến từ bài đăng trên blog của Bruce Dawson về chủ đề (link).

Vì bạn đang làm việc với C++, bạn có thể tạo một lớp RAII cho phép hoặc vô hiệu hóa ngoại lệ dấu phẩy động theo cách được sắp xếp. Điều này cho phép bạn có quyền kiểm soát tốt hơn để chỉ hiển thị trạng thái ngoại lệ cho mã của mình, thay vì tự quản lý gọi _controlfp_s() theo cách thủ công. Ngoài ra, trạng thái ngoại lệ dấu phẩy động được thiết lập theo cách này là hệ thống rộng, vì vậy bạn nên nhớ trạng thái trước đó của từ điều khiển và khôi phục nó khi cần. RAII có thể giải quyết vấn đề này cho bạn và là một giải pháp tốt cho các vấn đề với GDI + mà bạn mô tả.

Cờ ngoại lệ _EM_OVERFLOW, _EM_ZERODIVIDE và _EM_INVALID là điều quan trọng nhất cần giải thích. _EM_OVERFLOW được tăng lên khi vô cùng dương hoặc âm là kết quả của phép tính, trong khi _EM_INVALID được nâng lên khi kết quả là một NaN báo hiệu. _EM_UNDERFLOW an toàn để bỏ qua; nó báo hiệu khi kết quả tính toán của bạn khác 0 và giữa -FLT_MIN và FLT_MIN (nói cách khác, khi bạn tạo ra một sự bất thường). _EM_INEXACT được nâng lên quá thường xuyên để được sử dụng thực tế do bản chất của số học dấu chấm động, mặc dù nó có thể được cung cấp thông tin nếu cố gắng theo dõi kết quả không chính xác trong một số tình huống.

Mã SIMD thêm nhiều nếp nhăn hơn vào kết hợp; vì bạn không chỉ định sử dụng SIMD một cách rõ ràng, tôi sẽ bỏ qua một cuộc thảo luận về điều đó ngoại trừ việc lưu ý rằng chỉ định bất kỳ điều gì khác ngoài/fp: nhanh có thể vô hiệu hóa việc vectơ tự động mã của bạn trong VS 2012; xem this answer để biết chi tiết về điều này.

+0

Cảm ơn bạn đã gửi lời cảm ơn đến bài đăng trên blog của tôi. Họ điều khiển các chức năng thiết lập trạng thái của các cờ che dấu trong FPU, bật và tắt chúng. Đó là những gì kiểm soát xem các ngoại lệ FPU có bị che khuất hay không (và theo mặc định chúng là tất cả các mặt nạ Ram). Tôi tin rằng/fp: except và #pragma float_control yêu cầu trình biên dịch tạo mã tương thích với các ngoại lệ dấu phẩy động. Điều này rất quan trọng nếu bạn muốn bẫy chúng, xử lý chúng và tiếp tục. Nếu bạn đang sử dụng chúng chỉ để tìm lỗi (nếu bạn không cố gắng tiếp tục sau đó) thì chúng không quan trọng. –