如何加速向 ListView 添加项目? [英] How to speed adding items to a ListView?

查看:27
本文介绍了如何加速向 ListView 添加项目?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要向 WinForms ListView 添加几千个(例如 53,709 个)项目.

i'm adding a few thousand (e.g. 53,709) items to a WinForms ListView.

尝试 1:13,870 毫秒

foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}

这运行得非常糟糕.显而易见的第一个修复方法是调用 BeginUpdate/EndUpdate.

This runs very badly. The obvious first fix is to call BeginUpdate/EndUpdate.

尝试 2:3,106 毫秒

listView.BeginUpdate();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}
listView.EndUpdate();

这更好,但仍然太慢了一个数量级.让我们将创建 ListViewItems 与添加 ListViewItems 分开,这样我们才能找到真正的罪魁祸首:

This is better, but still an order of magnitude too slow. Let's separate creation of ListViewItems from adding ListViewItems, so we find the actual culprit:

尝试 3:2,631 毫秒

var items = new List<ListViewItem>();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   items.Add(item);
}

stopwatch.Start();

listView.BeginUpdate();
    foreach (ListViewItem item in items)
        listView.Items.Add(item));
listView.EndUpdate();

stopwatch.Stop()

真正的瓶颈是添加项目.让我们尝试将其转换为 AddRange 而不是 foreach

The real bottleneck is adding the items. Let's try converting it to AddRange rather than a foreach

尝试 4: 2,182 毫秒

listView.BeginUpdate();
listView.Items.AddRange(items.ToArray());
listView.EndUpdate();

好一点.让我们确保瓶颈不在 ToArray()

A bit better. Let's be sure that the bottleneck isn't in the ToArray()

尝试 5: 2,132 毫秒

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

listView.BeginUpdate();
listView.Items.AddRange(arr);
listView.EndUpdate();

stopwatch.Stop();

限制似乎是向列表视图添加项目.也许是 AddRange 的另一个重载,我们添加了一个 ListView.ListViewItemCollection 而不是数组

The limitation seems to be adding items to the listview. Maybe the other overload of AddRange, where we add a ListView.ListViewItemCollection rather than an array

尝试 6: 2,141 毫秒

listView.BeginUpdate();
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView);
lvic.AddRange(arr);
listView.EndUpdate();

那也好不到哪里去.

现在是伸展的时候了:

  • 第 1 步 - 确保没有将列设置为自动宽度":

  • Step 1 - make sure no column is set to "auto-width":

检查

第 2 步 - 确保 ListView 不会在每次添加项目时尝试对项目进行排序:

Step 2 - make sure the ListView isn't trying to sort the items each time i add one:

检查

第 3 步 - 询问 stackoverflow:

Step 3 - Ask stackoverflow:

检查

注意: 显然这个ListView不是虚拟模式;因为您不/不能将项目添加"到虚拟列表视图(您设置 VirtualListSize).幸运的是,我的问题不是关于虚拟模式下的列表视图.

Note: Obviously this ListView is not in virtual mode; since you don't/cannot "add" items to a virtual list view (you set the VirtualListSize). Fortunately my question is not about a list view in virtual mode.

是否有任何我遗漏的内容可能导致向列表视图添加项目如此缓慢?

Is there anything i am missing that might account for adding items to the listview being so slow?

奖金聊天

我知道 Windows ListView 类可以做得更好,因为我可以在 394 ms 中编写代码:

i know the Windows ListView class can do better, because i can write code that does it in 394 ms:

ListView1.Items.BeginUpdate;
for i := 1 to 53709 do
   ListView1.Items.Add();
ListView1.Items.EndUpdate;

与等效的 C# 代码 1,349 ms 相比:

which when compared to the equivalent C# code 1,349 ms:

listView.BeginUpdate();
for (int i = 1; i <= 53709; i++)
   listView.Items.Add(new ListViewItem());
listView.EndUpdate();

快了一个数量级.

我缺少 WinForms ListView 包装器的什么属性?

What property of the WinForms ListView wrapper am i missing?

推荐答案

我查看了列表视图的源代码,我注意到一些可能使性能降低 4 倍左右的事情你看到的是:

I took a look at the source code for the list view and I noticed a few things that may make the performance slow down by the factor of 4 or so that you're seeing:

在 ListView.cs 中,ListViewItemsCollection.AddRange 调用 ListViewNativeItemCollection.AddRange,这是我开始审计的地方

in ListView.cs, ListViewItemsCollection.AddRange calls ListViewNativeItemCollection.AddRange, which is where I began my audit

ListViewNativeItemCollection.AddRange(来自第 18120 行)有两次遍历整个值集合,一次收集所有已检查的项目,另一次在 InsertItems 被调用(它们都由对 owner.IsHandleCreated 的检查保护,所有者是 ListView)然后调用 BeginUpdate.

ListViewNativeItemCollection.AddRange (from line: 18120) has two passes through the entire collection of values, one to collect all the checked items another to 'restore' them after InsertItems is called (they're both guarded by a check against owner.IsHandleCreated, owner being the ListView) then calls BeginUpdate.

ListView.InsertItems(来自第 12952 行),第一次调用,对整个列表进行另一次遍历,然后调用 ArrayList.AddRange(可能是另一个传递),然后是另一个传递.导致

ListView.InsertItems (from line: 12952), first call, has another traverse of the entire list then ArrayList.AddRange is called (probably another pass there) then another pass after that. Leading to

ListView.InsertItems(来自第 12952 行),第二次调用(通过 EndUpdate)又一次通过,将它们添加到 HashTable,并且 Debug.Assert(!listItemsTable.ContainsKey(ItemId)) 会在调试模式下进一步减慢它的速度.如果没有创建句柄,它会将项目添加到 ArrayListlistItemsArrayif (IsHandleCreated),然后它会调用

ListView.InsertItems (from line: 12952), second call (via EndUpdate) another pass through where they are added to a HashTable, and a Debug.Assert(!listItemsTable.ContainsKey(ItemId)) will slow it further in debug mode. If the handle isn't created, it adds the items to an ArrayList, listItemsArray but if (IsHandleCreated), then it calls

ListView.InsertItemsNative(来自第 3848 行)最终通过列表,在那里它实际添加到本机列表视图中.Debug.Assert(this.Items.Contains(li) 会在调试模式下额外降低性能.

ListView.InsertItemsNative (from line: 3848) final pass through the list where it is actually added to the native listview. a Debug.Assert(this.Items.Contains(li) will additionally slow down performance in debug mode.

因此,在 .net 控件中实际将项目插入到本机列表视图之前,需要通过整个项目列表进行很多额外的传递.一些通行证通过检查正在创建的句柄来保护,因此如果您可以在创建句柄之前添加项目,则可能会节省一些时间.OnHandleCreated 方法采用 listItemsArray 并直接调用 InsertItemsNative,无需任何额外的麻烦.

So there are A LOT of extra passes through the entire list of items in the .net control before it ever gets to actually inserting the items into the native listview. Some of the passes are guarded by checks against the Handle being created, so if you can add items before the handle is created, it may save you some time. The OnHandleCreated method takes the listItemsArray and calls InsertItemsNative directly without all the extra fuss.

您可以阅读 参考来源自己看看,可能我漏了什么.

You can read the ListView code in the reference source yourself and take a look, maybe I missed something.

在MSDN 杂志 2006 年 3 月号有一篇名为Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps的文章.

本文包含提高 ListViews 性能的技巧等.这似乎表明在创建句柄之前添加项目会更快,但是在呈现控件时您将付出代价.也许应用评论中提到的渲染优化并在创建句柄之前添加项目将获得两全其美的效果.

This article contained tips for improving the performance of ListViews, among other things. It seems to indicate that its faster to add items before the handle is created, but that you will pay a price when the control is rendered. Perhaps applying the rendering optimizations mentioned in the comments and adding the items before the handle is created will get the best of both worlds.

以多种方式测试了这个假设,虽然在创建句柄之前添加项目非常快,但在创建句柄时却是指数级的慢.我试图欺骗它来创建句柄,然后以某种方式让它调用 InsertItemsNative 而不经过所有额外的传递,但可惜我被挫败了.我唯一能想到的可能是在 C++ 项目中创建 Win32 ListView,用项目填充它,并在创建其句柄时使用钩子捕获 ListView 发送的 CreateWindow 消息并传回对 win32 的引用ListView 而不是一个新窗口......但谁知道这会影响到什么......一个 Win32 大师需要说出这个疯狂的想法:)

Tested this hypothesis in a variety of ways, and while adding the items before creating the handle is suuuper fast, it is exponentially slower when it goes to create the handle. I played with trying to trick it to create the handle, then somehow get it to call InsertItemsNative without going through all the extra passes, but alas I've been thwarted. The only thing I could think might be possible, is to create your Win32 ListView in a c++ project, stuff it with items, and use hooking to capture the CreateWindow message sent by the ListView when creating its handle and pass back a reference to the win32 ListView instead of a new window.. but who knows what the side affects there would be... a Win32 guru would need to speak up about that crazy idea :)

这篇关于如何加速向 ListView 添加项目?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆