[過去ログ] Visual Studio 2008 Part 22 (314レス)
前次1-
抽出解除 レス栞

このスレッドは過去ログ倉庫に格納されています。
次スレ検索 歴削→次スレ 栞削→次スレ 過去ログメニュー
191
(14): 2018/09/15(土)20:37 ID:UR1d6CKz(3/5) AAS
ソース:
#include "stdafx.h"
#include <math.h>
using namespace System;

template<typename T> static double calc_norm_and_regulate(int num, T* r, bool regulate){ // <float> for debug.
double norm = 0;
for (int i=0;i<num;i++) norm += (double)r[i] * (double)r[i];
norm = sqrt(norm);
if (regulate) for (int i=0;i<num;i++) r[i] = (T)(r[i]/norm);
return norm;
}

int main(array<System::String ^> ^args)
{
int count = 16;
__int64 inputs_hex[16] = {
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x1fedb1530240aa54,
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x1ff0af0d95025bc3,
0x1fc9353df6af376b, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000};
double* inputs = (double*)inputs_hex;
double norm = calc_norm_and_regulate(count, inputs, false);
Console::Write(String::Format("{0:F6}, 0x{1:x16}\r\n",norm, *(__int64*)&norm));
// Release build
// 0.000000, 0x1ff68ddfb62221dd from IDE
// 0.000000, 0x1ff68ddfb62221de from command prompt
return 0;
}
195
(1): 2018/09/16(日)03:19 ID:wIV2HUNW(2/4) AAS
>>191
// Release build
// 0.000000, 0x1ff68ddfb62221dd from IDE
// 0.000000, 0x1ff68ddfb62221de from command prompt

それにしても、随分小さな値だね。ちなみに、浮動小数点表示
の場合の有効数字の桁数を上げたら、どのようになる?
1.xxxe-yy
表示にして。
196
(2): 2018/09/16(日)03:40 ID:wIV2HUNW(3/4) AAS
>>191
試しに、ソースの冒頭に
#include <stdio.h>
を追加してから、

Console::Write(String::Format("{0:F6}, 0x{1:x16}\r\n",norm, *(__int64*)&norm));
の部分を、

printf( "%30.30e, 0x%016X\n", norm, *(__int64*)&norm) );

としてみるとどうなる?
214: 2018/09/16(日)13:33 ID:zL1WUjLu(5/27) AAS
>>198
問題は、俺の環境で俺が提供したコード>>191だと、
同様に展開されないにも関わらず、『起動方法によって』結果が異なってしまう点だ。
俺の環境でのRelease/Debugの逆アセンブル結果のdiffは以下。
17c17
< 0000000c cmp dword ptr ds:[001C2E14h],0
---
> 0000000c cmp dword ptr ds:[00702E14h],0
19c19
< 00000015 call 68302BA9
---
> 00000015 call 683A5AB1
93c93
< 0000015a call FF6C3098
---
> 0000015a call FFCA57E8
98c98
< 0000016f push 0B5311Ch
---
> 0000016f push 0D03188h
104,105c104,105
< 00000183 push 4F9D68h
< 00000188 call FF6C30A4
---
> 00000183 push 2B71C0h
> 00000188 call FFCA57F4
アドレスの変更だけであり、君の結果
「ループ回数を決め打ちしたことによりアンローリングされ、一部の演算がx87精度で計算される」には該当しない。
そして、この状況でも結果が異なってしまうことが問題なのだ。

君は君が勝手に新しく作り込んだ問題に対し、間違った結論でお茶を濁したにすぎない。
君が知っているFPU関連のことはこちらも知っている上で、質問している。
228
(2): 2018/09/16(日)16:06 ID:zL1WUjLu(10/27) AAS
すまん、間違いの修正

>>224
× > どうやらこれが原因の可能性が出てきた。(はっきり言って俺のバグだが)
× > まあこれに当たっているのなら確実に俺のバグだし、これなら辻褄は合ってしまうのだが。

今回は俺はあくまで俺の本番コードのデバッグを念頭に置いていて、この発言だった。
ただし>>191の再現コードで『不定スタック領域』を掴んでいるわけもなく、
一応IDE起動とコマンドプロンプト起動での挙動の違いを再現出来ているわけだから、
これだけが問題ではないのも事実だ。

俺にとっては一つ新しい知見として、
・IDEから起動した場合、スタックが初期化されるっぽい
ということが分かった。とはいえOSは0fillしてから各プロセスにメモリを与えるので、実際は、
・コマンドプロンプト起動ならmain前に設定した続きでそのまま実行、
・IDE起動ならmain前に色々やって0fillして実行、
 或いはmain前に色々やることが多く、スタックが進み、(例えばデバッガをアタッチする為)
 結果的にOSが初期化済みの領域から始動
となって違いが発生するというところか。
230
(1): 2018/09/16(日)16:17 ID:haV9TZ8e(11/12) AAS
>>228
>ただし>>191の再現コードで『不定スタック領域』を掴んでいるわけもなく、
>一応IDE起動とコマンドプロンプト起動での挙動の違いを再現出来ているわけだから、
>これだけが問題ではないのも事実だ。

そうだよ。精度が変わるのはあなたの間違いではない。スタック領域が0クリア
されようがれまいが、あなたのコード自体には特に不安定さはない。
非初期化領域を参照しているコードは見当たらないし。
239
(1): 2018/09/16(日)16:49 ID:zL1WUjLu(16/27) AAS
>>237
いや、俺が提供した>>191のソースなら使われてるぞ。
>>200のソースでは使われてないが。

ただまあ、彼(200)がsqrtを落としたのも分からなくはない。
誤差が生じる=通常は桁落ちだから、この場合は当然積和部分が怪しい。
あらかじめ彼はそうなると分かっていてそれを落とし、予定調和的な結論にたどり着いてしまった。
それが彼の間違いだった、ということ。

俺は出来るだけ元のソースのままで追跡しようとしている。
元のソースの該当ケースと離れてしまっては意味がないから。
そして元ソースではsqrtを使っている。
241: 2018/09/16(日)16:54 ID:LrdaMWHl(3/5) AAS
>>237
ああ。また訂正。

sqrt()が使われていないのは、>>200 >>201 >>202 >>203 の場合で、
それは、ループ内にfprintf()を入れた場合と入れない場合とで、
x87 fpuレジスタのst(0)〜st(7)を使う「期間」が変わるために 80BITから
64BITへの書き戻し丸めの問題のために精度が変わっているだけだった。

一方、あなたが指摘した >>191 では、ちゃんと sqrt() 関数が使われていて、
それだと、IDEからの起動とコマンド・プロンプトからの起動とで、精度が変
わってくると。そして、その場合の逆アセンブル結果は >>235 のように
sqrt() 関数がその場で x87 fpu の fsqrt 命令を使わずに、call 文によって
実際に本当のサブ・ルーチンを呼び出していると。

これはとても興味深い。そのサブ・ルーチンの中が、時と場合によって
精度が変わってくるような書き方をされている可能性が見えてきた。
242
(1): 2018/09/16(日)16:56 ID:LrdaMWHl(4/5) AAS
>>239
>いや、俺が提供した>>191のソースなら使われてるぞ。
> >>200のソースでは使われてないが。

了解。

問題を切り分けるため、sqrt() を使わなかった場合の Release版での、
IDE起動とコマンドrライン起動の精度の違いを実験してみて欲しい。
249: 2018/09/16(日)20:54 ID:zL1WUjLu(21/27) AAS
>>240
さて再見したが、やはりstaticだけで直る理由は分からない。
なお、最適化ミスの場合は、逆アセンブラを読めば分かる。
今のところそれではない。

一応、>>191ソースのtemplate部の逆アセンブルを上げておく。(ただし重複するので頭のみ)
頭はこれ。続きが>>235,236

template<typename T> static double calc_norm_and_regulate(int num, T* r, bool regulate){ // <float> for debug.
double norm = 0;
00000000 55 push ebp
00000001 8B EC mov ebp,esp
00000003 83 EC 28 sub esp,28h
00000006 89 4D FC mov dword ptr [ebp-4],ecx
00000009 89 55 F8 mov dword ptr [ebp-8],edx
0000000c 83 3D 14 2E 76 00 00 cmp dword ptr ds:[00762E14h],0
00000013 74 05 je 0000001A
00000015 E8 FF 52 1B 68 call 681B5319
0000001a 33 D2 xor edx,edx
0000001c 89 55 E8 mov dword ptr [ebp-18h],edx
0000001f 33 D2 xor edx,edx
00000021 89 55 EC mov dword ptr [ebp-14h],edx
00000024 D9 EE fldz
00000026 DD 5D F0 fstp qword ptr [ebp-10h]
00000029 D9 EE fldz
0000002b DD 5D E0 fstp qword ptr [ebp-20h]
0000002e D9 EE fldz
00000030 DD 5D F0 fstp qword ptr [ebp-10h]
250: 2018/09/16(日)21:25 ID:zL1WUjLu(22/27) AAS
>>219
>>221
/MTと/clrは同時に指定出来ないらしい。(error D8016)
/MTdも同じく無理。

もう一つ /MDd ってのがあるから試してみた。

/MDdの結果:
Releaseビルドでコマンドプロンプト起動の時のみ ****de、
ReleaseビルドでIDEからの起動だと ***dd。(Debugビルドは起動方法を問わずこっち)
(/MDと全く挙動は同じ)

これで有効な指摘については全て回答してるかな?
見落としが有れば指摘よろしく。
(規制に引っかかったので遅くなってすまん)

今のところ、可能性があるのは以下か?

・Releaseビルドをコマンドプロンプトから起動したときのみなぜか精度が高い
 (>>200から結果的に検出された。今のところ精度が高いときと同じ挙動をしている為)
・ReleaseビルドもIDEから起動すれば結果的にスタックが0初期化されている状態になっており、
 俺の本番プログラムに関してはここに当たるバグがある?(>>228)
 (ただしこれは>>191には該当しない)
254: 2018/09/16(日)22:33 ID:zL1WUjLu(24/27) AAS
>>252
そちらの逆アセンブルは以下の違いが出てるだろ。
static版: fld/fmul/fadd/fstp
非static版: fld/fmul/faddp (fstpが無い)
この非static版の場合、拡張倍精度(80bit)で演算されるから精度が高いことになり、
static版との演算結果に違いが出るのも仕様通りなんだよ。(これは>>200と同じ間違い)

一応、fstpにも80bit版はあって、Intelのマニュアルによると以下。
> オペコード命令説明
> D9 /2 FST m32fp ST(0) をm32fp にコピーする。
> DD /2 FST m64fp ST(0) をm64fp にコピーする。
> DD D0+i FST ST(i) ST(0) をST(i) にコピーする。
> D9 /3 FSTP m32fp ST(0) をm32fp にコピーし、レジスタスタックをポップする。
> DD /3 FSTP m64fp ST(0) をm64fp にコピーし、レジスタスタックをポップする。
> DB /7 FSTP m80fp ST(0) をm80fp にコピーし、レジスタスタックをポップする。
> DD D8+i FSTP ST(i) ST(0) をST(i) にコピーし、レジスタスタックをポップする。
つまり君のstatic版
> 0000001f DD 1D 00 30 CC 00 fstp qword ptr ds:[00CC3000h]
では FSTP /3 m64fp [disp32] であり、そこで64bit(倍精度)に丸められてる。
だからレジスタ(80bit=拡張倍精度)で演算される非static版と結果が異なる。
static版のsftpが DB /7 m80fp なら誤差は出ないはずなんだよ。(Cでどう書くのかは知らん)

だから>>252の場合の誤差なら、仕様通りなんだよ。(片方が倍精度、もう片方は拡張倍精度)
ただし、>>191は逆アセンブル(>>235)を見る限りそれに該当しないし、(両方とも倍精度)
今回の俺の上記逆アセンブル(>>253、中身は君の指摘通りunmanagedにしただけ)も該当しない。(両方とも倍精度)
そして253は何故か直ってしまった。
286
(1): 2018/09/17(月)10:25 ID:+dwRu2dr(3/8) AAS
>>191がコンソール起動とIDE起動で挙動が異なる理由は分かりました。
ありがとう。

結論はつまり以下だ。
> JIT の最適化とデバッグ(抜粋)
> マネージ アプリケーションをデバッグするとき、Visual Studio では、既定で、
> ジャスト イン タイム (JIT: Just-In-Time) コードの最適化が省略されています。
> 最適化されたコードをデバッグするのは困難であるため、
> 最適化されたコードで発生するバグが、非最適化バージョンでは再現しないときにのみお勧めします。
> JIT 最適化は、Visual Studio の [モジュールの読み込み中に JIT 最適化を抑制する] オプションで制御されます。
> 実行中のプロセスにアタッチする場合、既に読み込まれ、JIT でコンパイルされ、
> 最適化されているコードが含まれることがあります。
> このようなコードの場合、[モジュールの読み込み中に JIT 最適化を抑制する] オプションの影響はありません。
> 外部リンク[aspx]:msdn.microsoft.com
確かにこのオプションで直った。
287: 2018/09/17(月)10:25 ID:+dwRu2dr(4/8) AAS
その他諸々、話を整理すると、以下となる。(ソースは>>191参照)
1. managedコードではMSILが出力され、x86コードは含まれていない。
2. 起動時、MSILはJITされ、x86コードに落とされる。
3. このため、mainの1行目でブレークポイントで止め、calc_norm_and_regulateの逆アセンブルを見ようとしても、
 IDE上で「逆アセンブルを表示できません。式がまだネイティブ マシン コードに翻訳されていません。」と出る。
 これはmainの1行目に System::Diagnostics::Debugger::Launch(); を入れたときも同様。
4. そしてこのJITに関して、上記IDE中の 『[モジュールの読み込み中に JIT 最適化を抑制する] オプション』 が効いてくる。
 規定ではオフ、つまり、ReleaseビルドでもIDE起動ならJIT最適化は抑制される。
 これがfld/fmul/fadd/fstpのループコードになる理由。
 これをオンにすれば、確かにReleaseビルドIDE起動でも、
 fld/fmul/faddのループコードとなり、コマンドプロンプト起動と同じ結果になることは確認した。
5. 上記では表現が微妙だが、JIT最適化をするかどうかは読み込まれるときに決まるらしい。
 したがって、Releaseビルドを起動後にアタッチした場合は通常通り最適化され、
 IDEからReleaseビルドを起動した場合は『既定では』最適化が抑制されてしまう。
 これがIDE起動とコマンドプロンプト起動で挙動が異なった原因。
 上記、『[モジュールの読み込み中に JIT 最適化を抑制する]』のチェックを外せば、直った。
6. おそらくこのオプションはソリューション毎ではなく、IDEのインストール毎なんだと思う。
 (ソリューション毎のオプションはプロジェクトのプロパティにあり、場所が違う)
 だからその人の環境によっては最初からオフにしている人がいたかも?
 これが再現実験をしてくれた人たちと微妙に結果が異なったりした原因か?

これで>>191についての疑問は解消した。(はず)
俺の本番コードについては再度確認し、また報告する。
303: 2018/09/17(月)18:30 ID:+dwRu2dr(8/8) AAS
さて俺の本番コード、以下のようだ。
疑問は解消した。協力してくれた皆様ありがとう。

◎:拡張倍精度、○:倍精度、として、(ソースは>>191参照)
・Releaseビルドをコマンドプロンプトから起動→◎積和、◎平方根
・Debugビルドをコマンドプロンプトから起動→◎積和、○平方根
・IDEから起動→○積和、○平方根

これで3種類出来上がってた。
(なお、>>166内バイナリをアタッチした際の「AまたはC」は、「AまたはB」の間違い)
そしてIDE上で『[モジュールの読み込み中に JIT 最適化を抑制する]』を変更すると、
確かにRelease/Debugの2種類に絞れる。
Debugだからといって、全く最適化がかからないわけでもないようだ。
(1行内なら最適化がかかる?)

参考に、Releaseビルドの該当部分の逆アセンブルは以下。
積和が拡張倍精度で行われ、そのまま fsqrt で平方根が取られる。
(関数ごとインライン化されているのでアドレスが中途半端だが)
double retval = calc_norm_and_regulate(count, vec, false);
0000003e fldz
00000040 xor edx,edx
00000042 test esi,esi
00000044 jle 00000056
00000046 lea eax,[esp+28h]
0000004a fld qword ptr [eax+edx*8]
0000004d fmul st(0),st
0000004f faddp st(1),st
00000051 inc edx
00000052 cmp edx,esi
00000054 jl 00000046
00000056 fsqrt
00000058 fstp qword ptr [esp+10h]
前次1-
スレ情報 赤レス抽出 画像レス抽出 歴の未読スレ AAサムネイル

ぬこの手 ぬこTOP 0.035s