为什么琴弦这么慢?


23

自从我上高中的第一门编程课开始,我就听说字符串操作比神话中的“平均操作”要慢,也就是成本更高。为什么让它们这么慢?(这个问题是故意留下的。)


11
如果您知道这些“平均操作”是神话,那么您至少可以告诉我们其中一些是什么吗?鉴于您在问这样一个模糊的问题,因此很难相信您的断言,这些未指定的操作确实是神话。
seh 2010年

1
@seh,不幸的是,我实际上无法回答。实际上,我有几次问人们哪些弦乐比慢,他们只是耸了耸肩,说:“他们只是慢。” 此外,如果我有更具体的信息,那将是SO而不是Programmers的问题。这已经是一个临界点。
流行

有什么意义呢?如果告诉字符串实际上很慢,您会停止使用它们吗?
图兰斯·科尔多瓦

算了吧。如果有人告诉你这样胡说八道,那么反问是:“真的吗?是吗?那我们应该使用整数数组吗?”
Ingo 2012年

Answers:


47

“平均操作”发生在基元上。但是,即使在将字符串视为原始语言的语言中,它们仍然是幕后的数组,进行涉及整个字符串的任何操作都需要O(N)时间,其中N是字符串的长度。

例如,将两个数字相加通常需要2-4条ASM指令。连接(“添加”)两个字符串需要新的内存分配以及一个或两个字符串副本,其中涉及整个字符串。

某些语言因素会使情况更糟。例如,在C语言中,字符串只是指向以null终止的字符数组的指针。这意味着您不知道它有多长时间,因此无法通过快速移动操作来优化字符串复制循环。您需要一次复制一个字符,以便可以测试每个字节的空终止符。


4
某些语言使它更好:Delphi对数组开头的字符串长度进行编码,使字符串连接非常快。
Frank Shearar

4
@gablin:它还可以使字符串自身的复制速度大大提高。当您预先知道大小时,就不必一次复制一个字节并检查每个字节是否有空终止符,因此可以使用任何寄存器(包括SIMD寄存器)的全部大小进行数据移动,从而它的速度提高了16倍。
梅森惠勒2010年

4
@mathepic:是的,这对您的影响是最大的,但是当您开始与libc或其他外部代码进行交互时,它期望的是char*,而不是a strbuf,然后您回到平方1。当将不良的设计植入语言时,可以做到这一点。
梅森惠勒2010年

6
@mathepic:当然buf指针在那里。我从来没有暗示过它不可用。而是有必要的。 任何不知道您的优化但非标准字符串类型的代码,包括诸如标准库之类的基本内容,仍然必须依靠缓慢,不安全的方法char*。您可以根据需要调用该FUD,但这并非不正确。
梅森惠勒2010年

7
人们,在Joel Spolsky专栏中有一篇关于Frank Shearer观点的文章:回到基础
user16764 2012年

14

这是一个老话题,我认为其他答案都不错,但忽略了一些东西,所以这是我的(晚)2美分。

句法糖衣隐藏了复杂性

字符串的问题在于,它们在大多数语言中都是二等公民,实际上大多数时候并不是语言规范本身的一部分:它们是由库实现的结构,顶部偶尔有句法糖衣使它们减轻了使用的痛苦。

这样的直接后果是,该语言将大部分复杂性隐藏在您的视线之外,并且您为偷偷摸摸的副作用付了钱,因为您养成了将它们视为低级原子实体的习惯,就像其他原始类型(如最高答案和其他解释)。

实施细节

好奥尔阵列

这种潜在的“复杂性”的要素之一是,大多数字符串实现都将诉诸使用带有一些连续存储空间的简单数据结构来表示字符串:好的ol'数组。

请注意,这很有意义,因为您希望快速访问整个字符串。但是,这意味着在您要操纵此字符串时可能会付出可怕的代价。如果您知道要在后面的索引,那么访问中间的元素可能会很快,但是基于条件查找元素却不是。

如果您的语言没有缓存字符串的长度,并且需要遍历字符串来计算字符,那么即使返回字符串的大小也可能会付出高昂的代价。

出于类似的原因,添加您的字符串元素会非常昂贵,因为您很可能需要重新分配一些内存才能执行此操作。

因此,不同的语言对这些问题采取不同的方法。例如,出于某些正当的理由(缓存长度,线程安全性),Java采取了使字符串不可变的自由,而对于可变的对应对象(StringBuffer和StringBuilder),它将选择使用较大的块来分配大小,而无需分配每次,但希望有最佳案例。通常效果很好,但缺点是有时要弥补内存影响。

Unicode支持

同样,这又是由于您的语言的语法糖衣使您无法玩得开心,您通常不认为它是unicode支持的术语(尤其是在您真正不需要它的情况下)然后撞到那堵墙)。而且,一些具有前瞻性的语言不会使用简单的8位char基元基本数组来实现字符串。它们采用UTF-8或UTF-16或您所需要的支持来进行烘焙,其结果是大大增加了内存消耗(通常不需要),并且分配内存,处理字符串的处理时间也更长,并实现与操纵代码点齐头并进的所有逻辑。


所有这些的结果是,当您在伪代码中执行等效的操作以:

hello = "hello,"
world = " world!"
str = hello + world

尽管语言开发人员已尽最大努力使它们表现得与您所期望的一样,但事实并非如此。

a = 1;
b = 2;
shouldBeThree = a + b

作为后续,您可能需要阅读:


当前讨论的好补充。
亚伯2012年

我刚刚意识到这是最好的答案,因为神话般的说法可以应用到任何类似RSA加密的方法中。将字符串放在这个尴尬的地方的唯一原因是因为plus运算符为大多数语言提供了字符串,这使新手不了解操作的成本。
Codism 2012年

@Abel:谢谢,在我看来,是留出更多通用细节的余地。
haylem 2012年

@ Codism:谢谢,很高兴您喜欢它。我确实确实认为这可以应用于很多情况,因为这些情况只是隐藏了一个复杂性问题(而且我们中的一些人不再关注底层细节,直到我们最终需要这样做时,因为遇到了瓶颈或砖墙)。
haylem 2012年

1

短语“平均操作”可能是理论上随机访问存储程序机器的单个操作的简写。这是通常用来分析各种算法的运行时间的理论机器。

通常将一般操作视为加载,加法,减法,存储,分支。也许还可以阅读,打印和暂停。

但是大多数字符串操作都需要其中一些基本操作。例如,复制字符串通常需要复制操作,因此需要进行与字符串长度成正比(即“线性”)的许多操作。在另一个字符串中查找子字符串也具有线性复杂度。


1

这完全取决于操作,如何表示字符串以及存在哪些优化。如果字符串的长度为4或8个字节(并对齐),则它们不一定会变慢-许多操作将与基元一样快。或者,如果所有字符串都具有32位或64位哈希,那么许多操作也将同样快(尽管您预先支付了哈希费用)。

这也取决于您所说的“慢”。大多数程序会根据需要快速处理字符串。字符串比较可能不如比较两个int那样快,但是只有通过性能分析才能揭示“慢”对程序的含义。


0

让我用一个问题回答你的问题。为什么说一串字比说一个字要花更长的时间?


2
不一定。
user16764 2012年

3
Supercalifragilisticexpialidocious
Spoike 2012年

s / word / syllable / g
Caleb

让我用一个问题回答您的问题:为什么不说答案的意思呢?毕竟,对于如何将其解释为适用于某些运行时系统还远远不够。
PJTraill '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.