草庐IT

go - 这台机器上最有效的 goroutines 数量

coder 2023-06-29 原文

所以我确实有一个由我编写的并发快速排序实现。它看起来像这样:

func Partition(A []int, p int, r int) int {
    index := MedianOf3(A, p, r)
    swapArray(A, index, r)
    x := A[r]
    j := p - 1
    i := p
    for i < r {
        if A[i] <= x {
            j++
            tmp := A[j]
            A[j] = A[i]
            A[i] = tmp
        }
        i++
    }
    swapArray(A, j+1, r)
    return j + 1
}


func ConcurrentQuicksort(A []int, p int, r int) {
    wg := sync.WaitGroup{}
    if p < r {
        q := Partition(A, p, r)
        select {
        case sem <- true:
            wg.Add(1)
            go func() {
                ConcurrentQuicksort(A, p, q-1)
                <-sem
                wg.Done()
            }()
        default:
            Quicksort(A, p, q-1)
        }
        select {
        case sem <- true:
            wg.Add(1)
            go func() {
                ConcurrentQuicksort(A, q+1, r)
                <-sem
                wg.Done()
            }()
        default:
            Quicksort(A, q+1, r)
        }
    }
    wg.Wait()
}

func Quicksort(A []int, p int, r int) {
    if p < r {
        q := Partition(A, p, r)
        Quicksort(A, p, q-1)
        Quicksort(A, q+1, r)
    }
}

我有一个 sem缓冲 channel ,我用它来限制运行的 goroutines 的数量(如果它达到这个数量,我不会设置另一个 goroutine,我只是在子数组上做普通的快速排序)。首先我从 100 开始,然后我更改为 50、20。基准会稍微好一点。但是在切换到 10 之后,它开始倒退,时间开始变大。因此,至少对于我的硬件而言,有一些任意数字可以使算法运行最高效。

当我实现这个时,我实际上看到了一些关于最好的 goroutines 数量的问题,现在我找不到它(愚蠢的 Chrome 历史实际上并没有保存所有访问过的站点)。你知道如何计算这样的事情吗?如果我不必对其进行硬编码,那就最好了,让程序自己完成。

P.S 我有非并发 Quicksort,它的运行速度比这慢 1.7 倍。正如你在我的代码中看到的,我做 Quicksort ,当运行的 goroutine 数量超过我之前设置的数量时。我想如何使用 ConcurrentQuicksort ,但不使用 go 调用它关键字,只需简单地调用它,也许如果其他 goroutine 完成它们的工作,ConcurrentQuicksort我称之为将开始启动 goroutines,加快进程(因为你可以看到 Quicksort 只会启动递归快速排序,没有 goroutines)。我这样做了,实际上时间比常规 Quicksort 慢了 10%。你知道为什么会这样吗?

最佳答案

您必须对这些东西进行一些试验,但我认为主要关注的不是 goroutines 一次运行。正如链接到的答案@reticentroot 所说,it's not necessarily a problem to run a lot of simultaneous goroutines .

我认为您主要关注的应该是 goroutine 启动的总数。当前的实现理论上可以启动一个 goroutine 来对几个项目进行排序,并且这个 goroutine 会在启动/协调上花费比实际排序更多的时间。

理想情况是,您只启动所需数量的 goroutine,以充分利用所有 CPU。如果您的工作项大小相同,并且内核也相同,那么每个内核启动一项任务是完美的。

在这里,任务的大小不均匀,因此您可以将排序拆分为比 CPU 多的任务并分配它们。 (在生产中,您通常会使用 worker pool 来分配工作,而无需为每个任务启动一个新的 goroutine,但我认为我们可以在这里跳过它。)

为了获得可行数量的任务——足以让所有核心保持忙碌,但又不会太多以至于你产生大量开销——你可以设置一个最小大小(初始数组大小/100 或其他),并且只拆分比这更大的数组。

稍微详细一点,每次将任务发送到后台时都会产生一些成本。对于初学者:

  • 每个 goroutine 启动都会花费一些时间来设置堆栈并进行调度程序簿记
  • 每个任务切换都会在调度程序中花费一些时间,并且可能会导致 cache misses当两个 goroutine 查看不同的代码或数据时
  • 您自己的协调代码( channel 发送和 sync 操作)需要时间

  • 其他事情可能会阻止理想的加速发生:您可能会达到系统范围的限制,例如正如 Volker 指出的那样,内存带宽会随着您添加内核而增加一些同步成本,并且您可能会遇到 various更棘手 issues有时。但是设置、切换和协调成本是一个很好的起点。

    当然,可以超过协调成本的好处是,其他 CPU 在闲置时可以完成工作。

    我认为,但尚未测试,您在 50 个 goroutine 上的问题是 1)您很久以前就已经达到了几乎完全利用的状态,因此添加更多任务会增加更多的协调工作而不会使事情变得更快,以及 2)您正在创建 goroutines对于微小的排序,它们可能花费更多的时间进行设置和协调,而不是实际进行排序。在 10 个 goroutine 时,您的问题可能是您不再实现 CPU 的充分利用。

    如果您愿意,您可以通过计算在各种 goroutine 限制(在 an atomic global counter 中)启动的 goroutine 总数并测量各种限制下的 CPU 利用率(例如,通过在 Linux/UNIX time 实用程序下运行您的程序)来测试这些理论.

    对于像这样的分而治之的问题,我建议的方法只是为足够大的子问题(对于快速排序,这意味着足够大的子数组) fork 一个 goroutine。您可以尝试不同的限制:也许您只为超过原始数组 1/64 的部分或高于某个静态阈值(如 1000 项)的部分启动 goroutine。

    你的意思是将这种排序例程作为练习,我怀疑,但是你可以做很多事情来使你的排序更快或更健壮地对抗奇怪的输入。 The standard libary sort对小子数组回退到插入排序,对导致快速排序问题的异常数据模式使用堆排序。

    您还可以查看其他算法,例如用于全部或部分排序的基数排序,which I played with .那个排序库也是并行的。在我将子数组交给其他 goroutine 进行排序之前,我使用了 127 个项目的最小截止值,并且我使用了 an arrangement with a fixed pool of goroutines and a buffered chan to pass tasks between them .这在当时产生了不错的实际加速,尽管当时它可能不是最好的方法,而且我几乎可以肯定它不在今天的 Go 调度程序中。实验很有趣!

    关于go - 这台机器上最有效的 goroutines 数量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44771078/

    有关go - 这台机器上最有效的 goroutines 数量的更多相关文章

    1. ruby - 如何进行排列以有效地定制输出 - 2

      这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

    2. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

      这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

    3. HBase Region 简介和建议数量&大小 - 2

      Region是HBase数据管理的基本单位,region有一点像关系型数据的分区。region中存储这用户的真实数据,而为了管理这些数据,HBase使用了RegionSever来管理region。Region的结构hbaseregion的大小设置默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的RegionServer,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的RegionServer。RegionSplit时机:当1个region中的某个Store下所有StoreFile

    4. python - 是否可以使用 Ruby 或 Python 禁用 anchor /引用来发出有效的 YAML? - 2

      是否可以在PyYAML或Ruby的Psych引擎中禁用创建anchor和引用(并有效地显式列出冗余数据)?也许我在网上搜索时遗漏了一些东西,但在Psych中似乎没有太多可用的选项,而且我也无法确定PyYAML是否允许这样做.基本原理是我必须序列化一些数据并将其以可读的形式传递给一个不是真正的技术同事进行手动验证。有些数据是多余的,但我需要以最明确的方式列出它们以提高可读性(anchor和引用是提高效率的好概念,但不是人类可读性)。Ruby和Python是我选择的工具,但如果有其他一些相当简单的方法来“展开”YAML文档,它可能就可以了。 最佳答案

    5. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

      require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

    6. ruby - 为什么这个救援语法有效? - 2

      好的,所以我有了我正在使用的应用程序的这种方法,它可以在生产中使用。我的问题为什么这行得通?这是新的Ruby语法吗?defeditload_elements(current_user)unlesscurrent_user.role?(:admin)respond_todo|format|format.json{render:json=>@user}format.xml{render:xml=>@user}format.htmlendrescueActiveRecord::RecordNotFoundrespond_to_not_found(:json,:xml,:html)end

    7. ruby-on-rails - 设计中的 ArgumentError::RegistrationsController#new 错误的参数数量(2 代表 0..1) - 2

      我在关注RyanbatesRailsCast的devise和omniauth(第235集-devise-and-omniauth-revised)。当我尝试使用Twitter登录时,标题中不断出现错误。defself.new_with_session(params,session)ifsession["devise.user_attributes"]new(session["devise.user_attributes"],without_protection:true)do|user|user.attributes=paramsuser.valid?end完整跟踪:C:/Ruby20

    8. ruby - 奇怪的 ruby​​ for 循环行为(为什么这样做有效) - 2

      defreverse(ary)result=[]forresult[0,0]inaryendresultendassert_equal["baz","bar","foo"],reverse(["foo","bar","baz"])这行得通,我想了解原因。有什么解释吗? 最佳答案 如果我使用each而不是for/in重写它,它看起来像这样:defreverse(ary)result=[]#forresult[0,0]inaryary.eachdo|item|result[0,0]=itemendresultendforainb基本上就

    9. ruby-on-rails - 如何计算 Ruby/Rails 中 JSON 对象的数量 - 2

      Ruby中如何“一般地”计算以下格式(有根、无根)的JSON对象的数量?一般来说,我的意思是元素可能不同(例如“标题”被称为其他东西)。没有根:{[{"title":"Post1","body":"Hello!"},{"title":"Post2","body":"Goodbye!"}]}根包裹:{"posts":[{"title":"Post1","body":"Hello!"},{"title":"Post2","body":"Goodbye!"}]} 最佳答案 首先,withoutroot代码不是有效的json格式。它将没有包

    10. arrays - 在两个数组中查找不相交元素的有效方法是什么? - 2

      我有以下数组:A=[1,2,3,4,5]B=[2,6,7,1]我想找到不相交的元素,如下:output=[3,4,5,6,7]我是这样实现的,output=A+B-(A&B)但它效率低下,因为我添加了两个数组,然后删除了公共(public)元素。它类似于查找不相交的元素。我能做得比这更好吗?如果是,怎么办? 最佳答案 如何只选择A中的元素而不是B中的元素以及B中的元素而不是A中的元素。(A-B)+(B-A) 关于arrays-在两个数组中查找不相交元素的有效方法是什么?,我们在Stack

    随机推荐