7

更新: 問題を再現しました! 下にスクロールしてコードを表示します。

クイックノート

  • 私の Core i5 CPU には 2 つのコア、ハイパースレッディングがあります。

  • を呼び出すと、プログラムがまだマルチスレッド化されていてもSetProcessAffinityMask(GetCurrentProcess(), 1)すべて問題ありません。

  • これを行わず、プログラムが Windows XP で実行されている場合 ( Windows 7 x64 では問題ありません!)、リスト ビューをスクロールしてアイコンをロードしている間、GUI が数秒間ロックし始めます。

問題

基本的に、以下に投稿されたプログラム (元のコードの縮小版) を Windows XP (Windows 7 で問題ありません) で実行すると、すべてのスレッドに同じ論理 CPUを強制しない限り、プログラムの UI が 0.5 秒遅れ始めます。とか、ぐらい。

:問題をさらに調査したため、この投稿の多くの編集がここにあります。)

スレッドの数は同じであることに注意してください。異なるのはアフィニティ マスクだけです。

メッセージパッシングの2つの異なる方法を使用してこれを試しました.組み込みとGetMessage独自のBackgroundWorker.

結果?1 つの論理 CPU へのアフィニティのBackgroundWorker 恩恵GetMessageを受けます (実質的にラグはありません) が、これによって完全に損なわれます (ラグは数秒の長さになります)。

なぜそれが起こるのか理解できません.複数のCPUは単一のCPUよりもうまく機能するべきではありませんか?!
スレッド数が同じ場合、なぜこのような遅延が発生するのでしょうか?


その他の統計:

GetLogicalProcessorInformation戻り値:

0x0: {ProcessorMask=0x0000000000000003 Relationship=RelationProcessorCore ...}
0x1: {ProcessorMask=0x0000000000000003 Relationship=RelationCache ...}
0x2: {ProcessorMask=0x0000000000000003 Relationship=RelationCache ...}
0x3: {ProcessorMask=0x0000000000000003 Relationship=RelationCache ...}
0x4: {ProcessorMask=0x000000000000000f Relationship=RelationProcessorPackage ...}
0x5: {ProcessorMask=0x000000000000000c Relationship=RelationProcessorCore ...}
0x6: {ProcessorMask=0x000000000000000c Relationship=RelationCache ...}
0x7: {ProcessorMask=0x000000000000000c Relationship=RelationCache ...}
0x8: {ProcessorMask=0x000000000000000c Relationship=RelationCache ...}
0x9: {ProcessorMask=0x000000000000000f Relationship=RelationCache ...}
0xa: {ProcessorMask=0x000000000000000f Relationship=RelationNumaNode ...}

コード

以下のコードは、Windows XP SP3 でのこの問題を示しています。(少なくとも、私のコンピュータではそうです!)

次の 2 つを比較してください。

  • 通常どおりプログラムを実行してから、スクロールします。ラグが表示されるはずです。

  • affinityコマンドライン引数を指定してプログラムを実行し、スクロールします。ほぼ完全に滑らかになるはずです。

なぜこれが起こるのでしょうか?

#define _WIN32_WINNT 0x502

#include <tchar.h>
#include <Windows.h>
#include <CommCtrl.h>

#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "user32.lib")

LONGLONG startTick = 0;

LONGLONG QPC()
{ LARGE_INTEGER v; QueryPerformanceCounter(&v); return v.QuadPart; }

LONGLONG QPF()
{ LARGE_INTEGER v; QueryPerformanceFrequency(&v); return v.QuadPart; }

bool logging = false;
bool const useWindowMessaging = true;   // GetMessage() or BackgroundWorker?
bool const autoScroll = false;   // for testing

class BackgroundWorker
{
    struct Thunk
    {
        virtual void operator()() = 0;
        virtual ~Thunk() { }
    };
    class CSLock
    {
        CRITICAL_SECTION& cs;
    public:
        CSLock(CRITICAL_SECTION& criticalSection)
            : cs(criticalSection)
        { EnterCriticalSection(&this->cs); }
        ~CSLock() { LeaveCriticalSection(&this->cs); }
    };
    template<typename T>
    class ScopedPtr
    {
        T *p;
        ScopedPtr(ScopedPtr const &) { }
        ScopedPtr &operator =(ScopedPtr const &) { }
    public:
        ScopedPtr() : p(NULL) { }
        explicit ScopedPtr(T *p) : p(p) { }
        ~ScopedPtr() { delete p; }
        T *operator ->() { return p; }
        T &operator *() { return *p; }
        ScopedPtr &operator =(T *p)
        {
            if (this->p != NULL) { __debugbreak(); }
            this->p = p;
            return *this;
        }
        operator T *const &() { return this->p; }
    };

    Thunk **const todo;
    size_t nToDo;
    CRITICAL_SECTION criticalSection;
    DWORD tid;
    HANDLE hThread, hSemaphore;
    volatile bool stop;
    static size_t const MAX_TASKS = 1 << 18;  // big enough for testing

    static DWORD CALLBACK entry(void *arg)
    { return ((BackgroundWorker *)arg)->process(); }

public:
    BackgroundWorker()
        : nToDo(0), todo(new Thunk *[MAX_TASKS]), stop(false), tid(0),
        hSemaphore(CreateSemaphore(NULL, 0, 1 << 30, NULL)),
        hThread(CreateThread(NULL, 0, entry, this, CREATE_SUSPENDED, &tid))
    {
        InitializeCriticalSection(&this->criticalSection);
        ResumeThread(this->hThread);
    }

    ~BackgroundWorker()
    {
        // Clear all the tasks
        this->stop = true;
        this->clear();
        LONG prev;
        if (!ReleaseSemaphore(this->hSemaphore, 1, &prev) ||
            WaitForSingleObject(this->hThread, INFINITE) != WAIT_OBJECT_0)
        { __debugbreak(); }
        CloseHandle(this->hSemaphore);
        CloseHandle(this->hThread);
        DeleteCriticalSection(&this->criticalSection);
        delete [] this->todo;
    }

    void clear()
    {
        CSLock lock(this->criticalSection);
        while (this->nToDo > 0)
        {
            delete this->todo[--this->nToDo];
        }
    }

    unsigned int process()
    {
        DWORD result;
        while ((result = WaitForSingleObject(this->hSemaphore, INFINITE))
            == WAIT_OBJECT_0)
        {
            if (this->stop) { result = ERROR_CANCELLED; break; }
            ScopedPtr<Thunk> next;
            {
                CSLock lock(this->criticalSection);
                if (this->nToDo > 0)
                {
                    next = this->todo[--this->nToDo];
                    this->todo[this->nToDo] = NULL;  // for debugging
                }
            }
            if (next) { (*next)(); }
        }
        return result;
    }

    template<typename Func>
    void add(Func const &func)
    {
        CSLock lock(this->criticalSection);
        struct FThunk : public virtual Thunk
        {
            Func func;
            FThunk(Func const &func) : func(func) { }
            void operator()() { this->func(); }
        };
        DWORD exitCode;
        if (GetExitCodeThread(this->hThread, &exitCode) &&
            exitCode == STILL_ACTIVE)
        {
            if (this->nToDo >= MAX_TASKS) { __debugbreak(); /*too many*/ }
            if (this->todo[this->nToDo] != NULL) { __debugbreak(); }
            this->todo[this->nToDo++] = new FThunk(func);
            LONG prev;
            if (!ReleaseSemaphore(this->hSemaphore, 1, &prev))
            { __debugbreak(); }
        }
        else { __debugbreak(); }
    }
};

LRESULT CALLBACK MyWindowProc(
    HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    enum { IDC_LISTVIEW = 101 };
    switch (uMsg)
    {
        case WM_CREATE:
        {
            RECT rc; GetClientRect(hWnd, &rc);

            HWND const hWndListView = CreateWindowEx(
                WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
                WS_CHILDWINDOW | WS_VISIBLE | LVS_REPORT |
                LVS_SHOWSELALWAYS | LVS_SINGLESEL | WS_TABSTOP,
                rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
                hWnd, (HMENU)IDC_LISTVIEW, NULL, NULL);

            int const cx = GetSystemMetrics(SM_CXSMICON),
                cy = GetSystemMetrics(SM_CYSMICON);

            HIMAGELIST const hImgList =
                ImageList_Create(
                    GetSystemMetrics(SM_CXSMICON),
                    GetSystemMetrics(SM_CYSMICON),
                    ILC_COLOR32, 1024, 1024);

            ImageList_AddIcon(hImgList, (HICON)LoadImage(
                NULL, IDI_INFORMATION, IMAGE_ICON, cx, cy, LR_SHARED));

            LVCOLUMN col = { LVCF_TEXT | LVCF_WIDTH, 0, 500, TEXT("Name") };
            ListView_InsertColumn(hWndListView, 0, &col);
            ListView_SetExtendedListViewStyle(hWndListView,
                LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
            ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL);

            for (int i = 0; i < (1 << 11); i++)
            {
                TCHAR text[128]; _stprintf(text, _T("Item %d"), i);
                LVITEM item =
                {
                    LVIF_IMAGE | LVIF_TEXT, i, 0, 0, 0,
                    text, 0, I_IMAGECALLBACK
                };
                ListView_InsertItem(hWndListView, &item);
            }

            if (autoScroll)
            {
                SetTimer(hWnd, 0, 1, NULL);
            }

            break;
        }
        case WM_TIMER:
        {
            HWND const hWndListView = GetDlgItem(hWnd, IDC_LISTVIEW);
            RECT rc; GetClientRect(hWndListView, &rc);
            if (!ListView_Scroll(hWndListView, 0, rc.bottom - rc.top))
            {
                KillTimer(hWnd, 0);
            }
            break;
        }
        case WM_NULL:
        {
            HWND const hWndListView = GetDlgItem(hWnd, IDC_LISTVIEW);
            int const iItem = (int)lParam;
            if (logging)
            {
                _tprintf(_T("@%I64lld ms:")
                    _T(" Received: #%d\n"),
                    (QPC() - startTick) * 1000 / QPF(), iItem);
            }
            int const iImage = 0;
            LVITEM const item = {LVIF_IMAGE, iItem, 0, 0, 0, NULL, 0, iImage};
            ListView_SetItem(hWndListView, &item);
            ListView_Update(hWndListView, iItem);
            break;
        }
        case WM_NOTIFY:
        {
            LPNMHDR const pNMHDR = (LPNMHDR)lParam;
            switch (pNMHDR->code)
            {
            case LVN_GETDISPINFO:
                {
                    NMLVDISPINFO *const pInfo = (NMLVDISPINFO *)lParam;
                    struct Callback
                    {
                        HWND hWnd;
                        int iItem;
                        void operator()()
                        {
                            if (logging)
                            {
                                _tprintf(_T("@%I64lld ms: Sent:     #%d\n"),
                                    (QPC() - startTick) * 1000 / QPF(),
                                    iItem);
                            }
                            PostMessage(hWnd, WM_NULL, 0, iItem);
                        }
                    };
                    if (pInfo->item.iImage == I_IMAGECALLBACK)
                    {
                        if (useWindowMessaging)
                        {
                            DWORD const tid =
                                (DWORD)GetWindowLongPtr(hWnd, GWLP_USERDATA);
                            PostThreadMessage(
                                tid, WM_NULL, 0, pInfo->item.iItem);
                        }
                        else
                        {
                            Callback callback = { hWnd, pInfo->item.iItem };
                            if (logging)
                            {
                                _tprintf(_T("@%I64lld ms: Queued:   #%d\n"),
                                    (QPC() - startTick) * 1000 / QPF(),
                                    pInfo->item.iItem);
                            }
                            ((BackgroundWorker *)
                             GetWindowLongPtr(hWnd, GWLP_USERDATA))
                                ->add(callback);
                        }
                    }
                    break;
                }
            }
            break;
        }
        
        case WM_CLOSE:
        {
            PostQuitMessage(0);
            break;
        }
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

DWORD WINAPI BackgroundWorkerThread(LPVOID lpParameter)
{
    HWND const hWnd = (HWND)lpParameter;
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0 && msg.message != WM_QUIT)
    {
        if (msg.message == WM_NULL)
        {
            PostMessage(hWnd, msg.message, msg.wParam, msg.lParam);
        }
    }
    return 0;
}

int _tmain(int argc, LPTSTR argv[])
{
    startTick = QPC();
    bool const affinity = argc >= 2 && _tcsicmp(argv[1], _T("affinity")) == 0;
    if (affinity)
    { SetProcessAffinityMask(GetCurrentProcess(), 1 << 0); }

    bool const log = logging;  // disable temporarily
    logging = false;

    WNDCLASS wndClass =
    {
        0, &MyWindowProc, 0, 0, NULL, NULL, LoadCursor(NULL, IDC_ARROW),
        GetSysColorBrush(COLOR_3DFACE), NULL, TEXT("MyClass")
    };
    HWND const hWnd = CreateWindow(
        MAKEINTATOM(RegisterClass(&wndClass)),
        affinity ? TEXT("Window (1 CPU)") : TEXT("Window (All CPUs)"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);

    BackgroundWorker iconLoader;
    DWORD tid = 0;
    if (useWindowMessaging)
    {
        CreateThread(NULL, 0, &BackgroundWorkerThread, (LPVOID)hWnd, 0, &tid);
        SetWindowLongPtr(hWnd, GWLP_USERDATA, tid);
    }
    else { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)&iconLoader); }
    
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        if (!IsDialogMessage(hWnd, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if (msg.message == WM_TIMER ||
            !PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
        { logging = log; }
    }

    PostThreadMessage(tid, WM_QUIT, 0, 0);
    return 0;
}
4

4 に答える 4

5

http://ideone.com/fa2fMに投稿したスレッド間のタイミングに基づくと、ここでは公平性の問題があるようです。この仮定のみに基づいて、認識された遅延の明らかな原因と、問題に対する潜在的な解決策に関する私の推論を以下に示します。

ウィンドウ プロシージャによって 1 つのスレッドで大量のLVN_GETDISPINFOメッセージが生成および処理されているように見えます。バックグラウンド ワーカー スレッドは、同じ速度でメッセージを維持してウィンドウにポストすることができますが、それがポストする WM_NULL メッセージ処理されるまでに時間がかかるほどキューに入っています。

プロセッサ アフィニティ マスクを設定すると、同じプロセッサが両方のスレッドにサービスを提供する必要があるため、システムの公平性が高まります。これにより、メッセージが生成される速度LVN_GETDISPINFOが非アフィニティ ケースと比較して制限されます。これは、WM_NULL メッセージをポストするときに、ウィンドウ プロシージャ メッセージ キューがそれほど深くない可能性が高いことを意味します。これは、メッセージが「より早く」処理されることを意味します。

どうにかしてキューイング効果をバイパスする必要があるようです。またはの代わりにを使用するSendMessageと、これを行うことができます。この場合、ウィンドウ プロシージャ スレッドが現在のメッセージを終了し、送信された WM_NULL メッセージを処理するまで、ワーカー スレッドはブロックされますが、メッセージ処理フローに WM_NULL メッセージをより均等に挿入することができます。キューに入れられたメッセージとキューに入れられていないメッセージの処理の説明については、このページを参照してください。SendMessageCallbackSendNotifyMessagePostMessageSendMessage

を使用することを選択しSendMessageたものの、 のブロック性のためにアイコンを取得できるレートを制限したくない場合はSendMessage、3 番目のスレッドを使用できます。I/O スレッドは 3 番目のスレッドにメッセージを投稿し、3 番目のスレッドはSendMessageアイコンの更新を UI スレッドに挿入するために使用します。このようにして、ウィンドウ プロセス メッセージ キューにアイコン リクエストをインターリーブする代わりに、満たされたアイコン リクエストのキューを制御できます。

Win7 と WinXP の動作の違いについては、Win7 でこの効果が見られない理由がいくつか考えられます。リスト ビュー コモン コントロールの実装が異なっており、LVN_GETDISPINFO メッセージが生成される速度が制限されている可能性があります。または、おそらく Win7 のスレッド スケジューリング メカニズムが、スレッド コンテキストをより頻繁に、またはより公平に切り替えます。

編集:

最新の変更に基づいて、次のことを試してください。

...

                struct Callback 
                { 
                    HWND hWnd; 
                    int iItem; 
                    void operator()() 
                    { 
                        if (logging) 
                        { 
                            _tprintf(_T("@%I64lld ms: Sent:     #%d\n"), 
                                (QPC() - startTick) * 1000 / QPF(), 
                                iItem); 
                        } 
                        SendNotifyMessage(hWnd, WM_NULL, 0, iItem); // <----
                    } 
                }; 


...

DWORD WINAPI BackgroundWorkerThread(LPVOID lpParameter) 
{ 
    HWND const hWnd = (HWND)lpParameter; 
    MSG msg; 
    while (GetMessage(&msg, NULL, 0, 0) > 0 && msg.message != WM_QUIT) 
    { 
        if (msg.message == WM_NULL) 
        { 
            SendNotifyMessage(hWnd, msg.message, msg.wParam, msg.lParam); // <----
        } 
    } 
    return 0; 
} 

編集2:

の代わりに をLVN_GETDISPINFO使用してメッセージがキューに配置されていることを確認した後は、それらをバイパスするために自分自身を使用することはできません。SendMessagePostMessageSendMessage

アイコンの結果がワーカー スレッドから返される前に wndproc によって処理されるメッセージが大量にあるという前提で引き続き作業を進めますが、これらの更新を準備ができたらすぐに処理する別の方法が必要です。

アイデアは次のとおりです。

  1. ワーカー スレッドは、同期されたキューのようなデータ構造に結果を配置しPostMessage、WM_NULL メッセージを ( を使用して) wndproc に投稿します (wndproc が将来いつか実行されるようにするため)。

  2. wndproc の先頭 (case ステートメントの前) で、UI スレッドは同期されたキューのようなデータ構造をチェックして、結果があるかどうかを確認します。結果がある場合は、キューのようなデータ構造から 1 つ以上の結果を削除して処理します。彼ら。

于 2012-07-10T17:34:17.713 に答える
2

この問題は、スレッドアフィニティとは関係がなく、リストアイテムを更新するたびにリストアイテムを更新する必要があることをリストビューに通知することと関係があります。ハンドラーにフラグLVIF_DI_SETITEMを追加せず、手動で呼び出すため、を呼び出す、リストビューはまだに設定されているアイテムを無効にします。pInfo->item.maskLVN_GETDISPINFOListView_UpdateListView_UpdateiImageI_IMAGECALLBACK

これは、次の2つの方法のいずれか(または両方の組み合わせ)で修正できます。

  1. WM_NULLハンドラーからListView_Updateを削除します。リストビューでは、画像を設定したときにハンドラーで画像を設定したアイテムが自動的に再描画WM_NULLされ、画像を2回以上設定していないアイテムは再描画されません。

  2. ハンドラーにLVIF_DI_SETITEMフラグを設定し、ではない値に設定しpInfo->item.maskます。LVN_GETDISPINFOpInfo->item.iImageI_IMAGECALLBACK

Vistaでページ全体をスクロールすると、同様のひどい動作が再現されます。上記のいずれかを実行すると、アイコンを非同期で更新しながら問題が修正されました。

于 2012-07-14T23:49:36.693 に答える
1
  • これが XP のハイパー スレッディング/論理コア スケジューリングに関連していると示唆するのはもっともらしいので、ハイパー スレッディングを無効にしてこれを試す IvoTops の提案を 2 番目にします。これを試して、私たちに知らせてください。

    なんで?なぜなら:

    a) 論理コアは、CPU バウンド タスクに対して不適切な並列処理を提供します。同じ物理コア上の 2 つの論理 HT コアで複数の CPU バウンド スレッドを実行すると、パフォーマンスが低下します。たとえば、このインテルの論文を参照してください。HTを有効にすると、典型的なサーバー スレッドで各リクエストのレイテンシまたは処理時間が増加する可能性があることを説明しています (ネット スループットは向上します)

    b) Windows 7 では、HT/SMT (対称マルチスレッド) スケジューリングが改善されています。ここにある Mark Russinovich のスライドでは、これについて簡単に説明しています。彼らは XP スケジューラが SMT 対応であると主張していますが、Windows 7 が明示的にこれに関する何かを修正しているという事実は、XP に何か欠けている可能性があることを意味します。したがって、OS がスレッド アフィニティを 2 番目のコアに適切に設定していないと推測しています。(おそらく、2 番目のスレッドをスケジュールする瞬間に 2 番目のコアがアイドル状態ではない可能性があるためです)。

  • あなたは、「プロセス (または個々のスレッド) の CPU アフィニティを、考えられるすべての組み合わせに、同じ論理 CPU と異なる論理 CPU で設定しようとしたところです」と書いています。

    これを設定したら、実際に 2 番目のコアで実行されていることを確認できますか?

    これは、タスク マネージャーまたは perfmon/perf カウンターで視覚的に確認できます。

    スレッドのアフィニティを設定するコードを投稿することもできます (SetProcessorAffinity の戻り値をチェックしていないことに注意してください。それもチェックしてください)。

    Windows のパフォーマンス カウンターが役に立たない場合、インテルの VTune パフォーマンス アナライザーはまさにこの種のものに役立ちます。

    タスク マネージャーを使用してスレッド アフィニティを手動で強制できると思います。

もう 1 つ: コア i5 は Nehalem または SandyBridge マイクロ アーキテクチャのいずれかです。Nehalem およびそれ以降の HT 実装は、前世代のアーキテクチャ (コアなど) とは大きく異なります。実際、Microsoft は、Nehalem より前のシステムで Biztalk サーバーを実行するために HT を無効にすることを推奨しています。そのため、おそらく Windows XP は新しい HT アーキテクチャをうまく処理できません。

于 2012-07-15T01:27:45.850 に答える
0

これは、ハイパースレッディングのバグである可能性があります。それが原因であるかどうかを確認するには、ハイパースレッディングをオフにして障害のあるプログラムを実行します (通常、BIOS でオフにすることができます)。過去 5 年間に、ハイパースレッディングが有効になっている場合にのみ表面化した 2 つの問題に遭遇しました。

于 2012-07-08T18:09:34.770 に答える