额外内存消耗b/w l+=c和l=c+l



我正在解决一个问题,在这个问题中,我必须多次在字符串前面添加一个字符,所以我只使用

string l ="";

char c = 'x';

l = c+l;

但当我运行它时,它显示超出了内存限制?相反,当我使用时

string l ="";

char c = 'x';

l += c;

reverse(l.begin(),l.end());

它已成功编译。我想知道为什么会发生这种事?

正如其他人所提到的,在字符串的前面添加一个字符将创建一个新的字符串并每次复制,而在后面添加一个查尔将以更大的步骤增加容量,并且只是偶尔复制。

但两种方式都使用<=2N内存。这两种方式使用的内存差别不大。添加到前面的问题是时间,而不是空间。

不过,测试可能注意到的是空闲内存的碎片。在第一种情况下,libc将为每个大小的字符串1-N分配一块内存,并立即再次释放它。但是,用于较小字符串的块不能用于较大字符串,除非释放两个相邻的小字符串并将其合并到可重用内存的较大块中。对于最后一步,你有一个大小为N-1和N的字符串。如果malloc只使用一个堆,那么最好的情况是你需要3N内存,最坏的情况是,你有大小为N-1的空闲内存块,大小为N-11的字符串,大小为N1的空闲块,大小N的字符串,尺寸为N-1的空闲块。所以整体3N-5N内存。

但是libc可能有一个优化的malloc,它使用不同的内存池来实现不同的分配大小(8、16、32、64…字节)。然后,分配给较小字符串的块将永远不会被重新用于较大字符串,您最终会得到2个大小分别为8、16、32、64。。。每个或者2N log_2N的不可用内存。尽管malloc的大小更大(页面大小的倍数),但它将对字符串所需的块进行mmap和munmap,并显示0开销。但对于较小的N,您很容易以>20N内存使用率。

对于l += creverse的情况,也存在同样的问题,但随着字符串以更大的步长增长,分配的数量要少得多。对于简单的malloc,您可能仍然需要3-4N内存,而对于优化的malloc,您可能最终只使用了N log_2N内存(假设字符串的大小翻倍)。或10N,其中第一种方法具有20N。

总之:两种方法都可以在大约相同的内存下运行,但无法或低效地重用释放的内存将对分配的总内存产生很大影响。杀死你的是内存系统的开销,而不是字符串使用的内存。

如果您真的想最大限度地减少内存使用,那么在字符串上使用reserve,使其在开始时分配字符串的最终大小。然后把所有的字符加起来,最后倒过来。然后你真的只需要N个内存。

c+l会产生一个新的std::string(临时)对象,该对象包含新的字符串内容,即以c为前缀的旧字符串的内容。

现在,这个std::string对象应该将字符串数据存储在哪里?它不能重用l的内存,因为此时我们还不知道该字符串将被分配给l。通常,我们可能希望将l与旧的字符串内容一起重用。

因此,这个临时字符串必须为整个(带前缀的)字符串分配内存,并将内容复制到其中

当然,然后我们将临时字符串分配给l,在这种情况下(因为临时字符串之后将不再可用)l可以重用临时对象使用的内存。

编译器可能会意识到这两个步骤最终只会有一个字符串对象,并且可能会跳过额外的分配,但我认为这通常是不可能的。

使用l += c;时,不会创建额外的字符串对象。相反,您直接附加到l。现在可能是l没有足够的内存来附加额外的c。在这种情况下,还可能重新分配内存,并将原始字符串内容复制到新的较大分配中。但这种情况只会发生在特定大小的CCD_ 24上。如果你重复添加这样的字符,那么这种情况只会偶尔发生,标准库用于重新分配内存的策略会使其在时间成本中只有一个不变的因素。然而,您可能仍然很幸运,所使用的最大内存没有超过限制。

如果需要重新分配,那么显示的两个代码片段应该使用大致相同的最大内存(在这个特定步骤中)。

当然,第二个例子会产生一个保留字符串。我想您可能打算在添加c之前将其反转。

不过,您可以继续使用c+l。您应该确保在该操作之后不再需要l的内容(因为l无论如何都会被c+l的结果替换)。这是用std::move:完成的

// for std::move
#include<utility>
/*...*/
l = c + std::move(l);

这应该与第二种情况下的内存使用量大致相同,但不需要将所有字符移动两次才能反转两次。

或者,正如@TedLingmo在评论中提到的,在开头添加字符的最直接方法可能是使用insert成员函数,该函数允许您指定要添加字符的确切位置:

l.insert(l.begin(), c);

这两者都不具有时间效率。如果必须在开头重复添加字符,而不是在结尾,那么最好只存储字符串的反转版本,并修改算法的其余部分以使用反转的字符串。附加到字符串的末尾通常很便宜,但附加到前面总是要花费时间。

l = c + l将分配一个新的字符串对象,然后覆盖旧的字符串对象(当您分配时)。

l += c将在适当的位置进行扩展。

假设l的长度为Nc的长度当然是一。

因此CCD_ 37的长度为CCD_。所以它使用了N+1空间,加上我们已经拥有的原始lN空间。所以2N+1的总空间。

l += c只是将其添加到位,所以我们只需要N+1的总空间。

(当然,我们不知道具体是如何实现的,但这应该会让您了解内存使用情况。)

最新更新