注释会减慢解释型语言的运行速度吗?

76

我之所以问这个问题是因为我使用 Python,但其他解释型语言(如 Ruby、PHP 和 JavaScript)可能也适用。

当我在代码中留下注释时,是否会减慢解释器的速度?根据我对解释器的有限理解,它将程序表达式作为字符串读入,然后将这些字符串转换为代码。每次解析注释时,似乎都浪费了时间。

这是真的吗?在解释型语言中是否有关于注释的约定,还是其影响可以忽略不计?


4
在我古老的Commodore 64上,BASIC语言确实存在这个问题。自那时以来,编程语言和硬件都有了巨大的进步。 - Fred Larson
7
你应该知道,“解释型语言”这个术语并没有太多实际意义。Python是以字节码编译的,而不是直接从源代码解释执行的。 - Thomas Wouters
考虑到这个问题,JavaScript可能是一个有趣的选择。例如,我相信JQuery有一个版本,它去掉了注释和额外的空格以最小化传输时间。 - Fred Larson
15
在JavaScript中删除注释和空格(并尽可能地压缩内容)相当普遍,但这不是为了加快解析或执行速度;它都与网络传输时间有关(以及对于繁忙的网站来说,带宽也很重要)。 - Thomas Wouters
4
http://www.google.com/index.html的源代码实际上是混淆的,因为Google将每个JS变量压缩到最多3个字母,并尽可能地去除了每一位空格。 - Nick T
11个回答

107

对于 Python,源文件在执行之前会被编译(生成 .pyc 文件),并且在此过程中注释被删除。因此,如果你有大量注释,它们会影响编译时间,但不会影响执行时间。


47
+1,因为我非常喜欢在这种情境下使用“gazillion”。 - M. Williams
3
在检测到这种情况之前,很难想象评论与代码的比率需要有多高。 - Mike Graham
5
@Mike:可能是1亿亿分之一吗? - Seth Johnson
1
虽然我不确定有多少个“gazillions”,但我认为你的想法是正确的。 - M. Williams
我只是想指出,即使编译时间只发生一次,然后就被缓存了。 - Ian Bicking

34

我写了一个简短的 Python 程序,就像这样:

for i in range (1,1000000):
    a = i*10

这个想法是,进行简单的计算很多次。

通过计时,运行时间为0.35±0.01秒。

然后我用整本《钦定版英文圣经》来重写了它,就像这样插入:

for i in range (1,1000000):
    """
The Old Testament of the King James Version of the Bible

The First Book of Moses:  Called Genesis


1:1 In the beginning God created the heaven and the earth.

1:2 And the earth was without form, and void; and darkness was upon
the face of the deep. And the Spirit of God moved upon the face of the
waters.

1:3 And God said, Let there be light: and there was light.

...
...
...
...

Even so, come, Lord Jesus.

22:21 The grace of our Lord Jesus Christ be with you all. Amen.
    """
    a = i*10

这次运行花费了0.4±0.05秒。

所以答案是。在循环中有4MB的注释确实会产生可测量的差异。


26
在同一篇帖子中,对于一项科学实验和《圣经》的内容给予加分。 - Fred Larson
58
这不是一条评论,而是一个字符串字面量。此外,如果您查看两个代码块的实际字节码,您将看到没有区别。该字符串只被解析一次,并且完全不参与计算。如果您将字符串放在循环外面,您应该会看到相同的减速效果。 - Thomas Wouters
16
+1 是用来抵消一个愚蠢的踩票,同时为你实际尝试而给予认可,尽管方法有缺陷。尽管抽象的讨论也很重要,但“试一试看看”经常会提供比它更好的答案。 - 3Dave
6
@David,这个测试案例并不是OP所描述的那个,也不代表任何人实际编写的代码。 - Mike Graham
4
@Rich,请将该字符串转换为评论,并发布新的时间表。 - smci
显示剩余9条评论

24

在解析阶段之前或之后通常会删除注释,而解析非常快,因此实际上注释不会减慢初始化时间。


11
为了使程序更快,必须删除注释。但是只有当注释非常大(MB? GB?)时,才会影响程序速度,一般情况下不会受到影响。 - Henrik Hansen
3
拥有数兆字节的注释意味着代码超过了数兆字节。实际分析和编译所需的时间将压倒“微不足道”的注释剥离时间。 - kennytm
12
我已经尝试了这个。在我特定的测试系统上,解析并执行约10兆字节的Python注释(和一个赋值语句)需要349毫秒。在这种情况下,源字节与时间的比率似乎是相当恒定的,大约为每毫秒28,000字节。在Codepad上运行的同一脚本较慢:http://codepad.org/Ckevfqmq。 - AKX
嗯,我相信人们可以构造一个反例。哦,看看Rich Bradshaw的答案。当然,就所有实际目的而言,你是完全正确的。 - janneb

6

这个效果在日常使用中可以忽略不计。测试很容易,但是如果你考虑一个简单的循环,比如:

For N = 1 To 100000: Next

你的电脑可以比你眨眼更快地处理那个(数到100,000)。忽略以某个特定字符开头的文本行将比这快10,000多倍。

不用担心。


5

我用类似Rich的脚本写了一些注释(只有约500kb的文本):

# -*- coding: iso-8859-15 -*-
import timeit

no_comments = """
a = 30
b = 40
for i in range(10):
    c = a**i * b**i
"""
yes_comment = """
a = 30
b = 40

# full HTML from http://en.wikipedia.org/
# wiki/Line_of_succession_to_the_British_throne

for i in range(10):
    c = a**i * b**i
"""
loopcomment = """
a = 30
b = 40

for i in range(10):
    # full HTML from http://en.wikipedia.org/
    # wiki/Line_of_succession_to_the_British_throne

    c = a**i * b**i
"""

t_n = timeit.Timer(stmt=no_comments)
t_y = timeit.Timer(stmt=yes_comment)
t_l = timeit.Timer(stmt=loopcomment)

print "Uncommented block takes %.2f usec/pass" % (
    1e6 * t_n.timeit(number=100000)/1e5)
print "Commented block takes %.2f usec/pass" % (
    1e6 * t_y.timeit(number=100000)/1e5)
print "Commented block (in loop) takes %.2f usec/pass" % (
    1e6 * t_l.timeit(number=100000)/1e5)


C:\Scripts>timecomment.py
Uncommented block takes 15.44 usec/pass
Commented block takes 15.38 usec/pass
Commented block (in loop) takes 15.57 usec/pass

C:\Scripts>timecomment.py
Uncommented block takes 15.10 usec/pass
Commented block takes 14.99 usec/pass
Commented block (in loop) takes 14.95 usec/pass

C:\Scripts>timecomment.py
Uncommented block takes 15.52 usec/pass
Commented block takes 15.42 usec/pass
Commented block (in loop) takes 15.45 usec/pass

根据David评论的修改:

 -*- coding: iso-8859-15 -*-
import timeit

init = "a = 30\nb = 40\n"
for_ = "for i in range(10):"
loop = "%sc = a**%s * b**%s"
historylesson = """
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
# blah blah...
# --></body></html> 
"""
tabhistorylesson = """
    # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    # blah blah...
    # --></body></html> 
"""

s_looped = init + "\n" + for_ + "\n" + tabhistorylesson + loop % ('   ','i','i')
s_unroll = init + "\n"
for i in range(10):
    s_unroll += historylesson + "\n" + loop % ('',i,i) + "\n"
t_looped = timeit.Timer(stmt=s_looped)
t_unroll = timeit.Timer(stmt=s_unroll)

print "Looped length: %i, unrolled: %i." % (len(s_looped), len(s_unroll))

print "For block takes %.2f usec/pass" % (
    1e6 * t_looped.timeit(number=100000)/1e5)
print "Unrolled it takes %.2f usec/pass" % (
    1e6 * t_unroll.timeit(number=100000)/1e5)


C:\Scripts>timecomment_unroll.py
Looped length: 623604, unrolled: 5881926.
For block takes 15.12 usec/pass
Unrolled it takes 14.21 usec/pass

C:\Scripts>timecomment_unroll.py
Looped length: 623604, unrolled: 5881926.
For block takes 15.43 usec/pass
Unrolled it takes 14.63 usec/pass

C:\Scripts>timecomment_unroll.py
Looped length: 623604, unrolled: 5881926.
For block takes 15.10 usec/pass
Unrolled it takes 14.22 usec/pass

@Nick,我期望任何非幼稚的解释器都只会在第一次循环中解析注释。你是否尝试过使用展开循环或者将几百行注释粘贴到代码中来实现这一点? - 3Dave

5
这取决于解释器的实现方式。现代的解释器在实际执行之前至少会对源代码进行一些预处理,其中包括剥离注释,因此从那时起注释不会产生任何影响。
曾经,当内存严重受限(例如,可寻址内存总量为64K,使用盒式磁带作为存储介质)时,你不能认为这样的事情是理所当然的。在Apple II、Commodore PET、TRS-80等计算机时代,程序员通常需要明确删除注释(甚至是空格),以提高执行速度。这只是当时通常采用的许多源代码级别的黑客之一。
当然,这也有助于这些机器拥有只能执行一条指令的CPU,时钟速度约为1 MHz,并且只有8位处理器寄存器。即使是现在只能在垃圾箱中找到的机器,它们的速度也比当时快得多,这甚至都不好笑...
1. 另一个例子,在Applesoft中,你可以根据行号的编号方式获得或失去一点速度。如果我没记错的话,当goto语句的目标是16的倍数时,速度会更快。

3
我对解释器的有限理解是,它将程序表达式作为字符串读取并将这些字符串转换为代码。
大多数解释器会读取文件中的文本(代码)并生成一个抽象语法树数据结构,因为下一阶段的编译可以轻松地读取该结构。该结构不包含任何以文本形式表示的代码和注释。只需该树就足以执行程序。但出于效率考虑,解释器会进一步生成字节码。Python正是如此。
我们可以说,在程序运行时,以你编写的形式呈现的代码和注释实际上是不存在的。因此,不,注释不会减慢程序的运行速度。
注意:那些没有使用某些其他内部结构来表示代码的解释器(例如语法树),必须像您提到的那样,在运行时不断进行解释。

2
拥有注释会减慢启动时间,因为脚本将被解析成可执行形式。然而,在大多数情况下,注释不会减慢运行时速度。
此外,在Python中,您可以将 .py 文件编译为 .pyc,其中不包含注释(我希望如此)- 这意味着如果脚本已经编译,则也不会有启动延迟。

在这种情况下,软件会使启动时间变慢不可测。在所有情况下,注释都不会使运行时间变慢。 - Mike Graham

0

正如其他回答所述,像 Python 这样的现代解释型语言首先会将源代码解析和编译成字节码,而解析器会忽略注释。这意味着在启动时实际解析源代码时才会出现任何速度损失。

由于解析器忽略注释,因此编译阶段基本上不受您放置的任何注释的影响。但是注释中的字节实际上是被读取并在解析过程中被跳过的。这意味着,如果您有大量注释(例如许多百兆字节),这将减慢解释器的速度。但是同样也会影响任何编译器。


我不确定严格意义上是否可以称之为“解释性语言”。类似动态编译或JIT似乎更为合适。 - 3Dave

0

我想知道评论的用法是否很重要。例如,三引号表示文档字符串。如果使用它们,则可以验证内容。不久前,我在将库导入我的Python 3代码时遇到了一个问题......我收到有关\N语法错误的错误。我查看了行号,发现是在三引号注释中的内容引起的。我有些惊讶。作为Python的新手,我从未想过块注释会被解释为语法错误。

简而言之,如果您键入:

'''
(i.e. \Device\NPF_..)
'''

Python 2不会报错,但是Python 3会报错: SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 14-15: malformed \N character escape

所以显然Python 3会解释三引号,并确保它是有效的语法。

然而,如果将其转换为单行注释:#(即\Device\NPF_..)
不会出现错误。

我想知道如果将三引号注释替换为单行注释,是否会有性能上的变化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接