7

更新:我已经重现了这个问题!向下滚动以查看代码。

快速笔记

  • 我的 Core i5 CPU 有 2 个内核,超线程。

  • 如果我打电话SetProcessAffinityMask(GetCurrentProcess(), 1)一切都很好即使程序仍然是多线程的。

  • 如果我这样做,并且程序在 Windows XP 上运行(在 Windows 7 x64 上很好!),我的 GUI 在我滚动列表视图和加载图标时开始锁定几秒钟。

问题

基本上,当我在 Windows XP(Windows 7 很好)上运行下面发布的程序(我的原始代码的简化版本)时,除非我为所有线程强制使用相同的逻辑 CPU ,否则程序 UI 开始滞后半秒或者。

注意:这里对这篇文章进行了很多编辑,因为我进一步调查了这个问题。)

请注意,线程数是相同的——只是关联掩码不同。

我已经尝试过使用两种不同的消息传递方法:内置的GetMessage以及我自己的BackgroundWorker.

结果?BackgroundWorker 受益于 1 个逻辑 CPU 的亲和性(几乎没有延迟),而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 上显示此问题。(至少,它可以在我的电脑上!)

比较这两个:

  • 正常运行程序,然后滚动。你应该看到滞后。

  • 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上发布的线程间时间,这里似乎存在公平问题。仅基于此假设,以下是我对感知滞后的明显原因和问题的潜在解决方案的推理。

看起来window proc在一个线程上生成和处理了大量LVN_GETDISPINFO消息,虽然后台工作线程能够跟上并以相同的速率将消息发布回窗口,但它发布的WM_NULL消息排在队列中很远,以至于需要时间才能得到处理。

LVN_GETDISPINFO当您设置处理器关联掩码时,您会在系统中引入更多公平性,因为同一个处理器必须为两个线程提供服务,这将限制相对于非关联情况生成消息的速率。这意味着当您发布 WM_NULL 消息时,window proc 消息队列可能没有那么深,这反过来意味着它们将被“更快”处理。

看来您需要以某种方式绕过排队效应。使用SendMessage,SendMessageCallbackSendNotifyMessage代替PostMessage可能是执行此操作的方法。在这种SendMessage情况下,您的工作线程将阻塞,直到窗口 proc 线程完成其当前消息并处理发送的 WM_NULL 消息,但您将能够更均匀地将 WM_NULL 消息注入消息处理流。有关排队与非排队消息处理的说明,请参阅此页面。

如果您选择使用SendMessage,但由于 的阻塞性质,您不想限制获取图标的速率SendMessage,那么您可以使用第三个线程。您的 I/O 线程会将消息发布到第三个线程,而第三个线程用于SendMessage将图标更新注入 UI 线程。通过这种方式,您可以控制满意的图标请求队列,而不是将它们交错到窗口 proc 消息队列中。

至于 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 线程检查同步的类似队列的数据结构以查看是否有任何结果,如果有,则从类似队列的数据结构中移除一个或多个结果并处理他们。

于 2012-07-10T17:34:17.713 回答
2

该问题与线程亲和性关系不大,而与告诉列表视图每次更新时它都需要更新列表项有关。因为您没有在处理程序中添加LVIF_DI_SETITEM标志,并且因为您手动调用,所以当您调用 时,列表视图会使仍然设置为的任何项目无效。pInfo->item.maskLVN_GETDISPINFOListView_UpdateListView_UpdateiImageI_IMAGECALLBACK

您可以通过以下两种方式之一(或两者结合)来解决此问题:

  1. WM_NULL从您的处理程序中删除 ListView_Update 。当您设置它们时,列表视图将自动重绘您在WM_NULL处理程序中为其设置图像的项目,并且它不会尝试重绘您多次未设置图像的项目。

  2. 在您的处理程序中设置LVIF_DI_SETITEM标志并设置为 not 的值。pInfo->item.maskLVN_GETDISPINFOpInfo->item.iImageI_IMAGECALLBACK

我重现了在 Vista 上进行整页滚动的类似可怕行为。执行上述任一操作都可以解决问题,同时仍异步更新图标。

于 2012-07-14T23:49:36.693 回答
1
  • 有理由认为这与 XP 的超线程/逻辑核心调度有关,我将支持 IvoTops 的建议,在禁用超线程的情况下尝试此操作。请试试这个并告诉我们。

    为什么?因为:

    a) 逻辑内核为 CPU 密集型任务提供了糟糕的并行性。在同一个物理内核上的两个逻辑 HT 内核上运行多个 CPU 绑定线程会损害性能。例如,请参阅这篇 intel 论文- 它解释了启用 HT 可能如何导致典型的服务器线程导致每个请求的延迟或处理时间增加(同时提高净吞吐量。)

    b) Windows 7 确实有一些 HT/SMT(对称多线程)调度改进。Mark Russinovich 的幻灯片在这里简要提到了这一点。尽管他们声称 XP 调度程序支持 SMT,但事实上 Windows 7 明确修复了一些问题,这意味着 XP 可能缺少一些东西。所以我猜测操作系统没有适当地将线程关联设置为第二个核心。(可能是因为第二个核心在调度第二个线程的那一刻可能不会空闲,因此推测)。

  • 您写道“我只是尝试将进程(甚至单个线程)的 CPU 亲和性设置为我能想到的所有可能的组合,在相同和不同的逻辑 CPU 上”。

    一旦你设置了这个,我们可以尝试验证执行实际上是在第二个核心上吗?

    您可以在任务管理器或 perfmon/perf 计数器中直观地检查这一点

    也许发布您设置线程亲和性的代码(我注意到您没有检查 SetProcessorAffinity 上的返回值,也请检查。)

    如果 Windows 性能计数器没有帮助,英特尔的 VTune 性能分析器对这类东西很有帮助。

    我认为您可以使用任务管理器手动强制线程关联。

还有一件事:您的核心 i5 是 Nehalem 或 SandyBridge 微架构。Nehalem 和后来的 HT 实现与前一代架构(Core 等)有很大不同。事实上,Microsoft 建议禁用 HT 以在 Nehalem 之前的系统上运行 Biztalk 服务器。所以也许 Windows XP 不能很好地处理新的 HT 架构。

于 2012-07-15T01:27:45.850 回答
0

这可能是一个超线程错误。要检查这是否是导致它在关闭超线程的情况下运行错误程序的原因(在 bios 中,您通常可以将其关闭)。在过去五年中,我遇到了两个问题,只有在启用超线程时才会出现。

于 2012-07-08T18:09:34.770 回答