Multithreading 一种多线程读、少线程写的高性能排序数据结构的设计
我有一个有趣的数据结构设计问题,这超出了我目前的专业知识。我正在寻找解决这个问题的数据结构或算法答案 要求:Multithreading 一种多线程读、少线程写的高性能排序数据结构的设计,multithreading,algorithm,delphi,data-structures,thread-safety,Multithreading,Algorithm,Delphi,Data Structures,Thread Safety,我有一个有趣的数据结构设计问题,这超出了我目前的专业知识。我正在寻找解决这个问题的数据结构或算法答案 要求: 在一个位置存储合理数量的(指针地址、大小)对(实际上是两个数字;第一个用作排序键) 在高度线程化的应用程序中,许多线程将查找值,以查看特定指针是否在(地址、大小)对中的一个内-也就是说,如果该对定义了内存范围,指针是否在列表中的任何范围内。线程很少在此列表中添加或删除条目 读取或搜索值必须尽可能快,每秒发生数十万到数百万次 添加或删除值(即改变列表)的情况要难得多;表现并不那么重要 列
- 在一个位置存储合理数量的
对(实际上是两个数字;第一个用作排序键)(指针地址、大小)
- 在高度线程化的应用程序中,许多线程将查找值,以查看特定指针是否在
对中的一个内-也就是说,如果该对定义了内存范围,指针是否在列表中的任何范围内。线程很少在此列表中添加或删除条目(地址、大小)
- 读取或搜索值必须尽可能快,每秒发生数十万到数百万次
- 添加或删除值(即改变列表)的情况要难得多;表现并不那么重要
- 列表内容过期是可以接受的,但并不理想,即线程的查找代码找不到应该存在的条目,只要该条目在某个点上存在
标记为Delphi,因为我使用该语言 这项任务。语言不可知论的答案非常受欢迎 但是,我可能无法使用任何标准 任何语言的图书馆都不需要太多的关注。原因是内存访问 (对象及其内存的分配、释放等,例如 树节点等)是严格控制的,必须通过我自己的 功能。同一程序中其他地方的当前代码使用 红/黑的树和一点黑,我自己写的。对象 节点分配通过自定义内存分配例程运行。 这超出了问题的范围,但这里提到它是为了避免 像“使用STL结构foo”这样的答案。我喜欢算法或 结构回答,只要我有正确的参考资料或教科书, 我可以实现我自己
我将使用
t字典
(来自Generics.Collections
)和TMREWSync
(来自SysUtils
)进行多读独占写访问TMREWSync
允许多个读卡器模拟访问词典,只要没有活动的编写器。字典本身提供了指针的O(1)查找
如果您不想使用RTL类,那么答案是:使用一个与多读独占写同步对象相结合的哈希映射
编辑:刚刚意识到您的对实际上代表内存范围,因此哈希映射不起作用。在这种情况下,您可以使用排序列表(按内存地址排序),然后使用二进制搜索快速查找匹配范围。这使得查找O(logn)而不是O(1)。稍微探索一下复制的想法 从正确性的角度来看,读写器锁将完成这项工作。然而, 在实践中,读者可以同时并行地进行阅读 在访问结构时,它们将在锁上创建一个巨大的争用,因为 很明显,即使对于读访问,锁定也涉及到对锁本身的写入。 这将降低多核系统的性能,甚至会降低多套接字的性能 系统 性能低下的原因是缓存线失效/传输流量 在磁芯/插座之间。(顺便说一下,这是一项非常近期的、非常有趣的研究 关于这个问题) 当然,我们可以通过 每个内核上的结构副本,并将读卡器线程限制为仅访问 本地复制到他们当前正在执行的核心。这需要某种机制使线程获得其当前的核心id。它还依赖于操作系统调度程序,以避免在核心之间无偿移动线程,即在某种程度上保持核心亲和力。 事实上,大多数当前的操作系统都能做到这一点 至于编写者,他们的工作将是更新所有现有副本,方法是获取每个写入锁。一次更新一棵树(显然结构应该是某棵树)确实意味着副本之间存在暂时的不一致性。从这个问题 说明此接缝可接受。当一个作者工作时,它会在一个单独的页面上阻止读者 核心,但不是所有的读者。缺点是,作者有权执行相同的工作 多次-系统中有内核或插槽时的次数 附言 也许,只是也许,另一种选择是类似于某种方法,但我不知道
这是很好的,所以我在提到它之后就停下来:)通过复制,您可以: -数据结构的一个副本(列表w/二进制搜索,提到的间隔树,…)(例如,“原始”数据结构),仅用于查找(读取访问)。 -第二个副本,即“更新”副本,在更改数据(写访问)时创建。因此,对更新副本进行写入 一旦编写完成,将一些“当前”指针从“原始”更改为“更新”版本。涉及到对“原始”副本的访问计数器,当计数器减回到零读卡器时,此副本可能会被销毁 在伪代码中:
// read:
data = get4Read();
... do the lookup
release4Read(data);
// write
data = get4Write();
... alter the data
release4Write(data);
// implementation:
// current is the datat structure + a 'readers' counter, initially set to '0'
get4Read() {
lock(current_lock) { // exclusive access to current
current.readers++; // one more reader
return current;
}
}
release4Read(copy) {
lock(current_lock) { // exclusive access to current
if(0 == --copy.readers) { // last reader
if(copy != current) { // it was the old, "original" one
delete(copy); // destroy it
}
}
}
}
get4Write() {
aquire_writelock(update_lock); // blocks concurrent writers!
var copy_from = get4Read();
var copy_to = deep_copy(copy_from);
copy_to.readers = 0;
return copy_to;
}
release4Write(data) {
lock(current_lock) { // exclusive access to current
var copy_from = current;
current = data;
}
release4Read(copy_from);
release_writelock(update_lock); // next write can come
}
要完成有关要使用的实际数据结构的回答,请执行以下操作:
考虑到数据项的固定大小(两个整数元组),也非常小,我将使用数组进行存储,并使用二进制搜索进行查找。(另一种选择是评论中提到的平衡树)
谈论性能:据我所知,“地址”和“大小”定义了范围。因此,查找f
Reader Shared data Writer
====== =========== ======
current = A:0
data = get4Read()
var copy = A:0
copy.readers++;
current = A:1
return A:1
data = A:1
... do the lookup
release4Read(copy == A:1):
--copy.readers current = A:0
0 == copy.readers -> true
data = get4Write():
aquire_writelock(update_lock)
var copy_from = get4Read():
var copy = A:0
copy.readers++;
current = A:1
return A:1
copy_from == A:1
var copy_to = deep_copy(A:1);
copy_to == B:1
return B:1
data == B:1
... alter the data
release4Write(data = B:1)
var copy_from = current;
copy_form == A:1
current = B:1
current = B:1
A:1 != B:1 -> true
delete A:1
!!! release4Read(A:1) !!!