昆布大好き!

主にプログラミングの技メモ

失敗しました Pythonでラムダ式

Pythonでプラス1する関数、プラス2する関数、…のリストを作ろうとして失敗しました。

#悪い例
ys = []
for i in range(4) :
    ys.append( lambda x: print(i+x) )
for f in ys :
    f(10)
print()
#良い例
ys = list(map( lambda x: lambda x2: print(x2+x), range(4) ))
for f in ys :
    f(10)

実行した結果は、

13
13
13
13

10
11
12
13

Windwos 8 64-bit, Python 3.4.2の結果です。

よくよく考えなおせば、まあ、そうなるな、という気がしてきます。

下半分をずうずうしく「良い例」などと書いてはいますが、はずかしいです。

偽共有 マルチスレッドの敵

マルチコアプロセッサの複数のコアが並走しても、キャッシュが共有であれば上手く並走しないことがあるようです。

あるスレッドがアクセス中のキャッシュラインを、別のスレッドが書き換えた場合、メモリの不整合が生じないようにキャッシュのデータをメモリに書き戻す、とのことです。

手っ取り早い話、マルチスレッドで、同じか近いメモリエリアにアクセスすると遅くなりますよ、ということです。

ただ、ずいぶんと捕らえ難い現象のようです。 とりあえず、再現してみました。

#include <iostream>
#include <thread>
#include <ctime>

unsigned char ary[1000];
const int N = 100000000;

void proc(int s)
{
    for ( int i=0; i<N; i++ ) {
        // インクリメントしてメモリを更新
        ary[s]++;
    }
}

int main()
{
    clock_t  s, e;

    // 隣のメモリを更新
    s = clock();
    std::thread  thr1(proc, 0), thr2(proc, 1);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 8バイト先のメモリを更新
    s = clock();
    thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 8);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 16バイト先
    s = clock();
    thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 16);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 32バイト先
    s = clock();
    thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 32);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 64バイト先
    s = clock();
     thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 64);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    return 0;
}

と、近いエリアから少しずつ離れたエリアのメモリを更新するようにしてみました。 CPUはCore2 Quad Q5550、OSはWindows 8 64bitでした。 結果はこう。

アクセス 時間[clock]
1バイト 567
8バイト 614
16バイト 531
32バイト 213
64バイト 213

32バイトくらいで差が出てきますね。 ただこれ最適化していない場合の結果で、したコードだと結果が違ってきます。 最適化して実行した場合、この程度の雑な処理ではclcokの値が0近くになるので計れません。ぐぬぬ

上のように、アドレスを正確に把握出来る場合はまだ良いですが、当然と言えば当然、こちらの記事.NET の問題 : 偽共有 のように、マネージドコードでも偽共有は起きます。

オブジェクトがヒープのどこに展開されるかなど知るわけありません。ますますもって度しがたいです。

TCP 対 UDP


TCPよりUDPがシンプルである、よってはやい。
などと疑いもなく信仰する人が多いように思いました。なのでやってみました。

 

 PC1  <----->  PC2

 

のような一対一のLAN直結ですが。

何故こんなつなぎ方をするのか。もっともだと思いますが。
まあ、ね。こう使う人もいるのですよ。

環境

PC1:
OS: Windows 8.1
CPU: Core 2 Quad Q9550 2.83GHz

PC2:
OS: Windows 7
CPU: Core i3 2330M

 

まず、100[MB]を1400バイトずつ分割して送信をループ(待ち時間なし)してみました。受信バッファは10240[B]です。

結果はこの通り。

(A) 1400[B] * n=100[MB]

TCP 0.95[sec] ほぼ同じ時間で受信
UDP 1.51[sec] 取りこぼすので受信時間は計測不能

 

さらに、TCPソケットはかなりのサイズを一度に送れるので、自分で分割などせず一度に送ればよいのです。

(B)100[MB] * 1

TCP 0.04[sec] 1.1[sec]で受信
UDP 不可能  

 

結果は、送信時間でTCPが数十倍速いです。
UDPソケットで待ち時間なしで複数送信した場合、受信しきれないことがほとんどなのですが、それでもなお、TCPで受信し終わるまでにUDP送信が終わらないのです。

 

送信の度にカーネルモードへの切り替えとデバイスへのメモリ転送が発生している。と思います。
特に後者がずいぶん待たされるため、何度も送信するUDPは時間がかかるのだと思っています。

 

もちろん、普通はネットワークの負荷によりTCPのACKが遅れたり迷子になったりするため、必ず早い、ということにはならないはずです。

 

おまけ

UDPの送信バッファサイズくらいまでは一度に送れるじゃないか。
そうすればもっと早く送れるじゃないか。
と、おもいやってみた結果。

分割サイズ時間
1400[B] 1.51[sec]
1400 * 2 [B] 0.98[sec]
1400 * 3[B] 14.3[sec]
1400* 4[B] 10.8[sec]

なぜだ。