草庐IT

memory-management - 在 Go 中使用 append 进行前置的机制是什么?

coder 2023-06-26 原文

假设我有一个 slice slice类型 int .在声明时,我将第三个参数设置为 size ,我相信它至少为 size 保留了内存ints 通过设置 cap slice 的参数。

slice:=make([]int,0,size)

现在,假设我有一个整数变量 value .要将其添加到最后的 slice 中,我使用
slice=append(slice,value)

如果当前 slice 中的元素数小于 size ,则无需将整个底层数组复制到新位置以添加新元素。

此外,如果我想添加 valueslice ,如建议 herehere , 我用
slice=append([]int{value},slice...)

我的问题是,在这种情况下会发生什么?如果元素数量仍然小于size ,元素是如何存储在内存中的?假设连续分配时 make()函数被调用,是否所有现有元素都右移以释放第一个空间值?还是重新分配内存并复制所有元素?

询问的原因是我希望我的程序尽可能快,并且想知道这是否是减慢它的可能原因。如果是这样,是否有任何替代方法可以更节省时间?

最佳答案

通过重新 slice 和复制

内置 append() 总是将元素 append 到 slice 。您不能(单独)使用它来添加元素。

话虽如此,如果您的 slice 容量大于长度(其元素后有“空闲”空间)并希望在其中添加元素,则可以重新 slice 原始 slice ,将所有元素复制到一个更高的索引以腾出空间对于新元素,然后将该元素设置为第 0 个索引。这将不需要新的分配。这是它的样子:

func prepend(dest []int, value int) []int {
    if cap(dest) > len(dest) {
        dest = dest[:len(dest)+1]
        copy(dest[1:], dest)
        dest[0] = value
        return dest
    }

    // No room, new slice need to be allocated:
    // Use some extra space for future:
    res := make([]int, len(dest)+1, len(dest)+5)
    res[0] = value
    copy(res[1:], dest)
    return res
}

测试它:
s := make([]int, 0, 5)
s = append(s, 1, 2, 3, 4)
fmt.Println(s)
s = prepend(s, 9)
fmt.Println(s)
s = prepend(s, 8)
fmt.Println(s)

输出(在 Go Playground 上试试):
[1 2 3 4]
[9 1 2 3 4]
[8 9 1 2 3 4]

注意:如果没有新元素的空间,因为性能现在很重要,我们不只是做:
res := append([]int{value}, dest...)

因为它进行了比需要更多的分配和复制:为文字( []int{value} )分配一个 slice ,然后是 append()追加时分配一个新的 dest到它。

相反,我们的解决方案只分配一个新数组(通过 make(),甚至为 future 增长保留一些空间),然后设置 value作为第一个元素并复制 dest (前面的元素)。

带链表

如果您需要多次添加,普通 slice 可能不是正确的选择。更快的替代方法是使用链表,在该链表中添加元素不需要分配 slice/数组和复制,您只需创建一个新的节点元素,然后通过将其指向旧的根(第一个元素)。

标准库在 container/list 中提供了通用实现。包裹。

手动管理更大的后备阵列

坚持使用普通 slice 和数组,还有另一种解决方案。

如果您愿意自己管理更大的后备数组(或 slice ),则可以通过在使用的 slice 之前留出可用空间来实现。预置时,您可以从支持的较大数组或 slice 中创建一个新的 slice 值,该值从一个索引开始,为 1 个要预置的元素留出空间。

不完整,仅作演示:
var backing = make([]int, 15) // 15 elements
var start int

func prepend(dest []int, value int) []int {
    if start == 0 {
        // No more room for new value, must allocate bigger backing array:
        newbacking := make([]int, len(backing)+5)
        start = 5
        copy(newbacking[5:], backing)
        backing = newbacking
    }

    start--
    dest = backing[start : start+len(dest)+1]
    dest[0] = value
    return dest
}

测试/使用它:
start = 5
s := backing[start:start] // empty slice, starting at idx=5
s = append(s, 1, 2, 3, 4)
fmt.Println(s)
s = prepend(s, 9)
fmt.Println(s)
s = prepend(s, 8)
fmt.Println(s)

// Prepend more to test reallocation:
for i := 10; i < 15; i++ {
    s = prepend(s, i)
}
fmt.Println(s)

输出(在 Go Playground 上试试):
[1 2 3 4]
[9 1 2 3 4]
[8 9 1 2 3 4]
[14 13 12 11 10 8 9 1 2 3 4]

分析:当backing有空间时,此方案不分配也不复制。 slice 以添加值! 所发生的一切就是从 backing 创建一个新 slice 覆盖目标 +1 空间的 slice ,用于要预先添加的值,设置它并返回此 slice 值。你真的不能比这更好了。

如果没有空间,则分配更大的backing slice ,复制旧内容,然后进行“正常”前置。

使用棘手的 slice

想法:想象一下你总是以倒序将元素存储在 slice 中。

在 slice 中以向后的顺序存储元素意味着 prepand 变为 append!

所以要“预加”一个元素,你可以简单地使用 append(s, value) .就这样。

是的,这有其有限的用途(例如, append 到以相反顺序存储的 slice 具有与“正常” slice 和前置操作相同的问题和复杂性),并且您失去了许多便利(使用 for range 列出元素的能力只是为了命名一个),但在性能方面没有什么比仅使用 append() 预先准备一个值更好的了.

注意:迭代以向后顺序存储元素的元素必须使用向下循环,例如:
for i := len(s) - 1; i >= 0; i-- {
    // do something with s[i]
}

最后一点:所有这些解决方案都可以很容易地扩展为预先添加一个 slice 而不仅仅是一个值。一般重新 slice 时的额外空间不是+1但是 +len(values) ,而不是简单地设置 dst[0] = value而是调用 copy(dst, values) .

关于memory-management - 在 Go 中使用 append 进行前置的机制是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41914386/

有关memory-management - 在 Go 中使用 append 进行前置的机制是什么?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  9. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  10. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

随机推荐