为什么要使用整数而不是长整数?

85

我经常看到与 相关的 Overflow 错误的问题。

我的问题是为什么要使用 integer 变量声明,而不是将所有数字变量(除了 double 等)定义为 long

除非你正在执行像 for 循环这样的操作,在这种情况下你可以保证值不会超过 32,767 的限制,否则是否使用 long 会影响性能或其他方面的因素吗?

9个回答

158
整数变量以16位(2字节)数字存储。 Office VBA参考 长整型变量作为带符号的32位(4字节)数字存储。 Office VBA参考 因此,好处在于减少内存空间。整数占用的内存只有长整数的一半。现在我们讨论的是2个字节,所以对于单个整数不会产生真正的影响,只有当您处理大量整数(例如大数组)且内存使用率很关键时才需要考虑。

BUT 在32位系统上,减半的内存使用量会带来性能成本。当处理器实际使用16位整数进行计算(例如增加循环计数器)时,值将被静默地转换为临时Long,而没有更大范围的数字可供使用。溢出仍然会发生,处理器用于存储计算值的寄存器将以相同的方式占用相同的内存(32位)。性能甚至可能受到损害,因为数据类型必须在非常低的级别上进行转换。

虽然这不是我要找的参考资料,但是...

我的理解是,即使声明为整数,底层的VB引擎也会将整数转换为长整数。因此,可能会注意到轻微的速度下降。我已经相信这一点有一段时间了,也许这也是为什么上面的语句被提出的原因,我没有询问原因。

ozgrid论坛

这是我正在寻找的参考资料。
简单来说,在32位系统中,2字节整数会被转换为4字节长整型。没有其他方式可以确保各个位正确地对齐以进行任何形式的处理。请考虑以下内容。
MsgBox Hex(-1) = Hex(65535) ' = True
显然,-1不等于65535,但计算机返回了正确的答案,即“FFFF”=“FFFF”。 然而,如果我们首先强制将-1转换为long,我们就会得到正确答案(65535大于32k,自动成为long)。
MsgBox Hex(-1&) = Hex(65535) ' = False

"FFFFFFFF" = "FFFF"

一般来说,在现代系统中,除了一些需要接收整数的遗留API之外,VBA中声明“As Integer”没有意义。 pcreview论坛 最终我找到了我真正想要的msdn文档
传统上,VBA程序员使用整数来保存小数字,因为它们需要更少的内存。然而,在最近的版本中,即使将它们声明为整数类型,VBA也会将所有整数值转换为长整型类型。因此,使用整数变量不再具有性能优势;实际上,长整型变量可能会稍微快一些,因为VBA不必转换它们。

根据评论澄清:整数仍然需要更少的内存来存储 - 一个大的整数数组将需要比具有相同维度的Long数组少得多的RAM(几乎正好是一半,您可以在任务管理器中自行检查)。但由于处理器需要使用32位内存块,VBA在执行计算时会临时将整数转换为长整型。


因此,现在几乎没有使用Integer类型的好理由。除非你需要与期望16位int的旧API调用进行Interop,或者你正在处理大量的小整数数组且内存非常紧张。
值得指出的一件事是,一些旧的API函数可能希望参数是16位(2字节)整数,如果你在32位上尝试通过引用传递一个整数(已经是4字节长),由于字节长度的差异,它将无法工作。
感谢Vba4All指出这一点。

6
值得一提的是,一些旧的API函数可能期望传入16位(2字节)整数参数。如果你正在32位系统上尝试通过引用传递一个已经是4字节长的整数,由于字节长度的不同,它将无法正常工作。 - user2140173
3
如果你把一个 Long 类型参数按引用传递给一个期望 Integer 类型参数的函数,它将使用前两个字节。由于数字是以小端模式存储的,所以这种方式可以正常工作(前提是只有低两个字节是有意义的,但如果这个 Long 只是以 32 位存储的整数,那么很可能就是这种情况)。 - user1220978
3
当声明一个Type时,有另一种情况必须使用Integer,这是因为类型的布局和大小很重要,无论是因为您要将该类型传递给API,还是因为您要对文件进行序列化/反序列化,或者使用LSet / Rset复制字节。 - ThunderFrame
1
你的回答似乎没有表明http://msdn.microsoft.com/en-us/library/office/aa164506(v=office.10).aspx可能是错误或误导的,而是将其呈现为“我真正寻找的msdn文档。”。然而,我的评论主要是针对未来的读者,而不是您自己,因为您曾参与那个问题的讨论(https://dev59.com/ll8d5IYBdhLWcg3wiSnI#26717521),尽管我认为您在那里的回答不应该被接受(我认为这个回答 https://dev59.com/ll8d5IYBdhLWcg3wiSnI#26717385 应该被接受) 。 - GSerg
1
谢谢Greedo。我想这是我第一次不必回滚此答案的编辑。 - RubberDuck
显示剩余6条评论

15

尽管这篇文章已经四年了,但我对此很感兴趣,所以进行了一些测试。最重要的是,程序员应该始终将变量声明为某个类型。未声明的变量明显表现最差(未声明的变量在技术上是Variant)。

Long 的表现最快,因此我认为微软建议始终使用 Long 而不是 Integer 是有道理的。 我猜测同样适用于 Byte,但大多数程序员不使用它。

64位Windows 10笔记本电脑上的结果

Variable Olympics

使用的代码:

Sub VariableOlymics()
'Run this macro as many times as you'd like, with an activesheet ready for data
'in cells B2 to D6
Dim beginTIME As Double, trials As Long, i As Long, p As Long

    trials = 1000000000
    p = 0

    beginTIME = Now
    For i = 1 To trials
        Call boomBYTE
    Next i
    Call Finished(p, Now - beginTIME, CDbl(trials))
    p = p + 1

    beginTIME = Now
    For i = 1 To trials
        Call boomINTEGER
    Next i
    Call Finished(p, Now - beginTIME, CDbl(trials))
    p = p + 1


    beginTIME = Now
    For i = 1 To trials
        Call boomLONG
    Next i
    Call Finished(p, Now - beginTIME, CDbl(trials))
    p = p + 1


    beginTIME = Now
    For i = 1 To trials
        Call boomDOUBLE
    Next i
    Call Finished(p, Now - beginTIME, CDbl(trials))
    p = p + 1


    beginTIME = Now
    For i = 1 To trials
        Call boomUNDECLARED
    Next i
    Call Finished(p, Now - beginTIME, CDbl(trials))
    p = p + 1

End Sub


Private Sub boomBYTE()
Dim a As Byte, b As Byte, c As Byte

    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1

End Sub


Private Sub boomINTEGER()
Dim a As Integer, b As Integer, c As Integer

    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1

End Sub


Private Sub boomLONG()
Dim a As Long, b As Long, c As Long

    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1

End Sub


Private Sub boomDOUBLE()
Dim a As Double, b As Double, c As Double

    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1

End Sub


Private Sub boomUNDECLARED()

    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1

End Sub

Private Sub Finished(i As Long, timeUSED As Double, trials As Double)

    With Range("B2").Offset(i, 0)
            .Value = .Value + trials
            .Offset(0, 1).Value = .Offset(0, 1).Value + timeUSED
            .Offset(0, 2).FormulaR1C1 = "=ROUND(RC[-1]*3600*24,0)"
    End With

End Sub

1
未声明的变量是一种变体。Dim a as Variant, b as Variant, c As Variant应该与根本不进行“dimming”(即不声明)的结果相同。 - Vityata
@Vityata,我知道“未声明”应该意味着变量,但请记住,这整个讨论的背景是微软自动将整数转换为Long,尽管已经定义了!因此,我不确定没有定义的整数会有什么结果。看到结果,似乎最有可能真正是变量,但这就是为什么我给出了“未声明”的名称而不是“变量”的原因。此外,这对于低级别的编码人员可能更有用。不过,我现在会添加进去。感谢您的反馈。 - pgSystemTester
@PGCodeRider - 创建一个带有C++的dll库并将其添加到VBA中进行基准测试可能会很有趣。结果可能会很有趣。我在这里做了类似的事情 - https://www.vitoshacademy.com/vba-vs-c-function-which-one-is-faster-in-excel/ - Vityata

13

正如其他回答所提到的,int和long之间的真正区别在于其内存空间大小,因此它可以容纳的数字大小不同。

这里是有关这些数据类型的完整文档 http://msdn.microsoft.com/en-us/library/office/ms474284(v=office.14).aspx

一个整数是16位,可以表示-32,768到32,767之间的值

长整型为32位,可以表示-2,147,483,648到2,147,483,647之间的值

还有一个长长整型,为64位,可以处理像9百万亿这样大的数字

记住的最重要的事情之一是,数据类型因语言和操作系统/平台而异。在您的VBA世界中,long是32位,但在64位处理器上的c#中,long是64位。这可能会导致显着的混淆。

虽然VBA不支持,但当您转移到任何其他语言中,如.NET或Java等,我更喜欢使用系统数据类型 int16 int32 int64 ,这使我能够更透明地了解这些数据类型可以容纳的值。


8

VBA有很多历史包袱。

Integer是16位宽,当16位架构/字长普遍存在时,它是一个很好的默认数字类型。

Long是32位宽,(在我看来)应该尽可能使用。


6
我采用了@PGSystemTester的方法并进行了更新,以消除一些潜在的变异性。通过将循环放置在例程中,这将消除调用例程所需的时间(这需要很长时间)。我还关闭了屏幕更新,以消除可能导致的任何延迟。
Long仍然表现最佳,由于这些结果更接近于仅受变量类型影响,因此值得注意的是变异的大小。
我的结果(桌面,Windows 7,Excel 2010):

enter image description here

使用的代码:

Option Explicit

Sub VariableOlympics()
'Run this macro as many times as you'd like, with an activesheet ready for data
'in cells B2 to D6
Dim beginTIME As Double, trials As Long, i As Long, p As Long
Dim chosenWorksheet As Worksheet

    Set chosenWorksheet = ThisWorkbook.Sheets("TimeTrialInfo")

    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    Application.ScreenUpdating = False

    trials = 1000000000 ' 1,000,000,000 - not 10,000,000,000 as used by @PGSystemTester

    p = 0

    beginTIME = Now
    boomBYTE trials
    Finished p, Now - beginTIME, CDbl(trials), chosenWorksheet.Range("B2")
    p = p + 1

    beginTIME = Now
    boomINTEGER trials
    Finished p, Now - beginTIME, CDbl(trials), chosenWorksheet.Range("B2")
    p = p + 1


    beginTIME = Now
    boomLONG trials
    Finished p, Now - beginTIME, CDbl(trials), chosenWorksheet.Range("B2")
    p = p + 1


    beginTIME = Now
    boomDOUBLE trials
    Finished p, Now - beginTIME, CDbl(trials), chosenWorksheet.Range("B2")
    p = p + 1


    beginTIME = Now
    boomUNDECLARED trials
    Finished p, Now - beginTIME, CDbl(trials), chosenWorksheet.Range("B2")
    p = p + 1

    Application.EnableEvents = True
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
    chosenWorksheet.Calculate

End Sub


Private Sub boomBYTE(numTrials As Long)
Dim a As Byte, b As Byte, c As Byte

Dim i As Long
For i = 1 To numTrials
    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1
Next i

End Sub


Private Sub boomINTEGER(numTrials As Long)
Dim a As Integer, b As Integer, c As Integer

Dim i As Long
For i = 1 To numTrials
    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1
Next i

End Sub


Private Sub boomLONG(numTrials As Long)
Dim a As Long, b As Long, c As Long

Dim i As Long
For i = 1 To numTrials
    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1
Next i

End Sub


Private Sub boomDOUBLE(numTrials As Long)
Dim a As Double, b As Double, c As Double

Dim i As Long
For i = 1 To numTrials
    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1
Next i

End Sub


Private Sub boomUNDECLARED(numTrials As Long)
Dim a As Variant, b As Variant, c As Variant

Dim i As Long
For i = 1 To numTrials
    a = 1
    b = 1 + a
    c = 1 + b
    c = c + 1
Next i

End Sub

Private Sub Finished(i As Long, timeUSED As Double, trials As Double, initialCell As Range)

    With initialCell.Offset(i, 0)
            .Value = trials
            .Offset(0, 1).Value = timeUSED
            .Offset(0, 2).FormulaR1C1 = "=ROUND(RC[-1]*3600*24,2)"
    End With

End Sub

我实际上更喜欢这种方法,胜过我的。 - pgSystemTester
你好,您是否有关于PostgreSQL云实例的性能测试结果?您使用的是哪个版本的PostgreSQL?您能否解释一下Long与Integer的最佳结果机制?谢谢。 - Oleg Ushakov
@OlegUshakov:请查看被接受的答案以了解机制。您可以在您喜欢的环境中轻松地进行测试。 - AJD
@AJD 或者,非常抱歉,我以为你是在为 PostgreSQL 环境编写测试。我没有注意到与 Excel 和 VBA 脚本相关的详细信息。我对我的疏忽深感抱歉。 - Oleg Ushakov

5
这是一个“空间”和“必要性”的问题。
在某些情况下,使用long类型是必要的。例如,如果你正在遍历一个大型Excel文件中的行,那么保存行号的变量应该是long类型。
然而,有时候你会知道整数足以解决问题,使用long类型就会浪费空间(内存)。单个变量的差别不大,但处理数组时可能会有很大的差异。
在VBA7中,整数类型占用2个字节,而长整型占用4个字节。如果你有一个包含100万个1到10之间数字的数组,使用整数类型数组将占用大约2MB的RAM,而长整型数组则占用大约4MB的RAM。

1
对于一个介于1到10之间的数字数组,你可以使用BYTE数组代替,但我理解你想表达的意思。 - ChrisB
这可能在15年前是正确的,但现在答案已经过时了。 - Vityata
这个已经过时了吗?在 Pandas 处理数百万行数据(大数据/机器学习)或在网络传输大量数据时存在问题(例如物理实验)。 - Alter
@Alter - 我猜你没理解原始问题的重点。看一下被接受的答案。 - Vityata
1
看起来我理解了。此外,被接受的答案指出当它说没有区别时,它是在谈论32位系统。 - Alter

2
正如其他人已经提到的,一个Long可能会占用两倍于一个Integer的空间。正如其他人也已经提到的,当前计算机的高容量意味着你将看不到任何性能差异,除非你处理的数据量非常非常大:
内存
考虑到1百万个值,使用Integers与Longs之间的差异将是每个值2个字节,因此这是2 * 1,000,000 / 1,024 / 1024 = 小于2MB的RAM差异,这很可能远小于您RAM容量的1%甚至0.1%。
处理
考虑到PGSystemTester的基准测试,当处理每个包含4个操作的100亿批次时,您可以看到Long和Integer之间的66秒差异。将数字降低到1百万个操作,我们可以期望66 / 10,000 / 4 = 执行时间少于2ms的差异。
我个人使用Integers和Longs来帮助我的代码可读性,特别是在循环中,其中Integer表示预计循环较小(少于1000次迭代),而Long告诉我循环预计要相当大(超过1000次)。
请注意,这个主观的阈值远低于Integer的上限,我使用Longs只是为了区分自己对小和大的定义。

0

感谢 AJDpgSystemTester。我确认了对于整数33长整型20的AJD结果。然而,长整型要快得多,只是因为您的测试程序足够小,可以完全适合处理器缓存内存。最快的RAM是L1缓存,其中数据和程序各有32kB。对于一个或几个数组,它们几乎适合于32kB的L1数据缓存,测试Byte、Integer和Long将会给出完全不同或相反的速度结果。

在我的情况下,对于相同的数组,整数总共120kB,长整型总共240kB,我得到了与整数相同的长整型结果。这是因为将相同的数组更改为长整型,总大小增加了一倍,与整数数组相比,更多的数据由于更改为长整型而落在L1缓存之外。要访问L1缓存之外的数据需要更多的时钟或时间。

因此,您的测试只是作为测试而言很好,但在实际生活中却会误导人们,因为msdn.microsoft建议使用Long。此外,那些强调Long内存大小加倍的人并没有强调处理器等待时间达到L1缓存之外甚至更糟的L2缓存或L3缓存之外的数据的后果。对于每个L1、L2和L3之外的数据,到达数据的时间将急剧增加,这对速度最为重要。
总结一下:
1.如果您的数据适合放在L1缓存中,则Long是最快的,但这仅限于4k的数据乘以Long的4字节= 16kB(因为其他程序和操作系统将填充其余32k的L1缓存);
2.对于至少16kB大小的数组,Byte和Integer将大幅提高速度,因为更改为Long将增加大小,这将迫使更多的数据驻留在最快的L1缓存RAM之外。
尝试相同的测试,但不要使用Dim a As Byte,而是使用Dim a() As Byte,例如:
Dim a() As Byte, b() As Byte, c() As Byte
ReDim a(7, 24, 60), b(24, 7, 60), c(24, 60, 7)
Dim h As Long, loops As Long: Dim i As Long, j As Long, k As Long   '   these i, j, k always As Long
loops=1
For h = 1 To loops
    For i = 1 To 6: For j = 0 To 23: For k = 1 To 58
a(i, j, k) = a(i + 1, j, k): b(j, i, k) = b(j, i - 1, k)
c(j, k, i) = a(i - 1, j, k + 1) + b(j, i - 1, k - 1)
    Next k: Next j: Next i
    For i = 6 To 1 Step -1: For j = 23 To 0 Step -1: For k = 58 To 1 Step -1
a(i, j, k) = a(i + 1, j, k): b(j, i, k) = b(j, i - 1, k)
c(j, k, i) = a(i - 1, j, k + 1) + b(j, i - 1, k - 1)
    Next k: Next j: Next i
Next h

首先将“循环”设置为1,以查看所需时间。然后逐渐增加它,以达到几秒钟的字节大小。对于As Integer,需要更长的时间,对于As Long则需要更长的时间...

每个3个数组的大小为8x25x61 = 12200,这是

12200 kB乘以3 = 36600 kB用于As Byte,

24400 kB乘以3 = 73200 kB用于As Integer,

48800 kB乘以3 = 146400 kB用于As Long。

使用Dim a() As Integer,b() As Integer,c() As Integer运行相同的代码,然后使用Dim a() As Long,b() As Long,c() As Long等。

现在,如果您将一个维度增加20倍,则预计持续时间将增加20倍,但实际上会更多,因为现在数据将超出L2缓存(4个核心共享1MB)。

如果您将一个维度增加200倍,则预计持续时间将增加200倍,但实际上会再次更多,因为现在数据将超出L3缓存(4个核心和8个核心共享6-8MB,而ryzen 5800则为8MB或16MB...)。

我不明白为什么经过20年或更长时间后,L1缓存仅为64kB,而它至少可以是16x16=256kB。使用16位行地址和16位列地址,您只需要读取32位,这是32位处理器的一次读取。我怀疑这是因为核心仍在使用16位(8位行+8位列地址,8x8=64kB)或更糟糕的是仅使用8位。

测试后请发布您的结果。


0

我知道这是一个旧帖子,但我在浏览时发现了它,我也想分享我的发现: 在@PGSystemTester的方法之后,我得到了以下结果 enter image description here

而在AJD之后

enter image description here

Intel i5-8500T CPU 8GB RAM 64位系统和Win10Enterprise 21H1操作系统版本19043.2006 Excel版本为2108,构建版本为14326.20852

我也不知道它是否有影响,但我也得到了Rubberduck Vers. 2.5.2.5906

但似乎我的整数在两种情况下都更快


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