MemoryLayout<T>.size/stride/alignment 是编译时吗?
Is MemoryLayout<T>.size/stride/alignment compile time?
作为参考,在C/C++中,等价的(sizeof
运算符)是编译时的,可以与模板编程(泛型)一起使用。
我在 Swift Algorithms Club 中寻找通用数据结构的实现,偶然发现了他们对位集的实现:
public struct BitSet {
private(set) public var size: Int
private let N = 64
public typealias Word = UInt64
fileprivate(set) public var words: [Word]
public init(size: Int) {
precondition(size > 0)
self.size = size
// Round up the count to the next multiple of 64.
let n = (size + (N-1)) / N
words = [Word](repeating: 0, count: n)
}
<clipped>
在 sizeof 作为编译时常量运算符存在的语言中,我会将 N
设置为 sizeof(Word) * 8
或者 MemoryLayout<UInt64>.size * 8
而不是 "magic number" 64。我承认,它在这里不是很神奇,但如果只是为了使它在语义上清楚发生了什么,那么这一点就成立了。
此外,我注意到函数族,同样的问题也适用 (ref)。
static func size(ofValue value: T) -> Int
static func stride(ofValue value: T) -> Int
static func alignment(ofValue value: T) -> Int
编辑:从 generic/not 函数的通用版本添加一些反汇编。
非泛型 swift:
func getSizeOfInt() -> Int {
return MemoryLayout<UInt64>.size
}
产生这个反汇编:
(lldb) disassemble --frame
MemoryLayout`getSizeOfInt() -> Int:
0x1000013c0 <+0>: pushq %rbp
0x1000013c1 <+1>: movq %rsp, %rbp
0x1000013c4 <+4>: movl [=15=]x8, %eax
-> 0x1000013c9 <+9>: popq %rbp
0x1000013ca <+10>: retq
基于 0x8 常量,根据@Charles Srstka 的回答,这看起来像是 编译时间 常量。
通用 swift 实现怎么样?
func getSizeOf<T>(_ t:T) -> Int {
return MemoryLayout<T>.size
}
产生了这个反汇编:
(lldb) disassemble --frame
MemoryLayout`getSizeOf<A> (A) -> Int:
0x100001390 <+0>: pushq %rbp
0x100001391 <+1>: movq %rsp, %rbp
0x100001394 <+4>: subq [=17=]x20, %rsp
0x100001398 <+8>: movq %rsi, -0x8(%rbp)
0x10000139c <+12>: movq %rdi, -0x10(%rbp)
-> 0x1000013a0 <+16>: movq -0x8(%rsi), %rax
0x1000013a4 <+20>: movq 0x88(%rax), %rcx
0x1000013ab <+27>: movq %rcx, -0x18(%rbp)
0x1000013af <+31>: callq *0x20(%rax)
0x1000013b2 <+34>: movq -0x18(%rbp), %rax
0x1000013b6 <+38>: addq [=17=]x20, %rsp
0x1000013ba <+42>: popq %rbp
0x1000013bb <+43>: retq
0x1000013bc <+44>: nopl (%rax)
以上看起来不是编译时间...?我还不熟悉 mac/lldb.
上的汇编程序
此代码:
func getSizeOfInt64() -> Int {
return MemoryLayout<Int64>.size
}
生成此程序集:
MyApp`getSizeOfInt64():
0x1000015a0 <+0>: pushq %rbp
0x1000015a1 <+1>: movq %rsp, %rbp
0x1000015a4 <+4>: movl [=11=]x8, %eax
0x1000015a9 <+9>: popq %rbp
0x1000015aa <+10>: retq
由此可见,MemoryLayout<Int64>.size
确实是编译时
检查 stride
和 alignment
的程序集留给 reader,但它们给出相似的结果(实际上相同,在 Int64
的情况下)。
编辑:
如果我们谈论的是泛型函数,显然还需要做更多的工作,因为函数在编译时不知道其大小的类型,因此不能只放入常量。但是,如果您将函数定义为采用 type 而不是该类型的实例,那么它所做的工作会比您的示例少一些:
func getSizeOf<T>(_: T.Type) -> Int {
return MemoryLayout<T>.size
}
像这样称呼:getSizeOf(UInt64.self)
生成此程序集:
MyApp`getSizeOf<A>(_:):
0x100001590 <+0>: pushq %rbp
0x100001591 <+1>: movq %rsp, %rbp
0x100001594 <+4>: movq %rsi, -0x8(%rbp)
0x100001598 <+8>: movq %rdi, -0x10(%rbp)
-> 0x10000159c <+12>: movq -0x8(%rsi), %rsi
0x1000015a0 <+16>: movq 0x88(%rsi), %rax
0x1000015a7 <+23>: popq %rbp
0x1000015a8 <+24>: retq
作为参考,在C/C++中,等价的(sizeof
运算符)是编译时的,可以与模板编程(泛型)一起使用。
我在 Swift Algorithms Club 中寻找通用数据结构的实现,偶然发现了他们对位集的实现:
public struct BitSet {
private(set) public var size: Int
private let N = 64
public typealias Word = UInt64
fileprivate(set) public var words: [Word]
public init(size: Int) {
precondition(size > 0)
self.size = size
// Round up the count to the next multiple of 64.
let n = (size + (N-1)) / N
words = [Word](repeating: 0, count: n)
}
<clipped>
在 sizeof 作为编译时常量运算符存在的语言中,我会将 N
设置为 sizeof(Word) * 8
或者 MemoryLayout<UInt64>.size * 8
而不是 "magic number" 64。我承认,它在这里不是很神奇,但如果只是为了使它在语义上清楚发生了什么,那么这一点就成立了。
此外,我注意到函数族,同样的问题也适用 (ref)。
static func size(ofValue value: T) -> Int
static func stride(ofValue value: T) -> Int
static func alignment(ofValue value: T) -> Int
编辑:从 generic/not 函数的通用版本添加一些反汇编。
非泛型 swift:
func getSizeOfInt() -> Int {
return MemoryLayout<UInt64>.size
}
产生这个反汇编:
(lldb) disassemble --frame
MemoryLayout`getSizeOfInt() -> Int:
0x1000013c0 <+0>: pushq %rbp
0x1000013c1 <+1>: movq %rsp, %rbp
0x1000013c4 <+4>: movl [=15=]x8, %eax
-> 0x1000013c9 <+9>: popq %rbp
0x1000013ca <+10>: retq
基于 0x8 常量,根据@Charles Srstka 的回答,这看起来像是 编译时间 常量。
通用 swift 实现怎么样?
func getSizeOf<T>(_ t:T) -> Int {
return MemoryLayout<T>.size
}
产生了这个反汇编:
(lldb) disassemble --frame
MemoryLayout`getSizeOf<A> (A) -> Int:
0x100001390 <+0>: pushq %rbp
0x100001391 <+1>: movq %rsp, %rbp
0x100001394 <+4>: subq [=17=]x20, %rsp
0x100001398 <+8>: movq %rsi, -0x8(%rbp)
0x10000139c <+12>: movq %rdi, -0x10(%rbp)
-> 0x1000013a0 <+16>: movq -0x8(%rsi), %rax
0x1000013a4 <+20>: movq 0x88(%rax), %rcx
0x1000013ab <+27>: movq %rcx, -0x18(%rbp)
0x1000013af <+31>: callq *0x20(%rax)
0x1000013b2 <+34>: movq -0x18(%rbp), %rax
0x1000013b6 <+38>: addq [=17=]x20, %rsp
0x1000013ba <+42>: popq %rbp
0x1000013bb <+43>: retq
0x1000013bc <+44>: nopl (%rax)
以上看起来不是编译时间...?我还不熟悉 mac/lldb.
上的汇编程序此代码:
func getSizeOfInt64() -> Int {
return MemoryLayout<Int64>.size
}
生成此程序集:
MyApp`getSizeOfInt64():
0x1000015a0 <+0>: pushq %rbp
0x1000015a1 <+1>: movq %rsp, %rbp
0x1000015a4 <+4>: movl [=11=]x8, %eax
0x1000015a9 <+9>: popq %rbp
0x1000015aa <+10>: retq
由此可见,MemoryLayout<Int64>.size
确实是编译时
检查 stride
和 alignment
的程序集留给 reader,但它们给出相似的结果(实际上相同,在 Int64
的情况下)。
编辑:
如果我们谈论的是泛型函数,显然还需要做更多的工作,因为函数在编译时不知道其大小的类型,因此不能只放入常量。但是,如果您将函数定义为采用 type 而不是该类型的实例,那么它所做的工作会比您的示例少一些:
func getSizeOf<T>(_: T.Type) -> Int {
return MemoryLayout<T>.size
}
像这样称呼:getSizeOf(UInt64.self)
生成此程序集:
MyApp`getSizeOf<A>(_:):
0x100001590 <+0>: pushq %rbp
0x100001591 <+1>: movq %rsp, %rbp
0x100001594 <+4>: movq %rsi, -0x8(%rbp)
0x100001598 <+8>: movq %rdi, -0x10(%rbp)
-> 0x10000159c <+12>: movq -0x8(%rsi), %rsi
0x1000015a0 <+16>: movq 0x88(%rsi), %rax
0x1000015a7 <+23>: popq %rbp
0x1000015a8 <+24>: retq