是MemoryLayout <t> .size / stride / alignment编译时间吗?

时间:2017-09-04 23:46:39

标签: swift memory-layout

作为参考,在C / C ++中,等效(sizeof运算符)是编译时,可以与模板编程(泛型)一起使用。

我正在通过Swift算法俱乐部查看常见数据结构的实现,并遇到了他们的位集实现:

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而不是“幻数”64.我承认,它在这里并不是很神奇,但重点在于它是否只是为了让它在语义上清晰可见。

此外,我注意到相同问题适用的函数系列(ref)。

static func size(ofValue value: T) -> Int
static func stride(ofValue value: T) -> Int
static func alignment(ofValue value: T) -> Int

编辑:从通用/非通用版本的函数中添加一些反汇编。

非通用的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   $0x8, %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   $0x20, %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   $0x20, %rsp
    0x1000013ba <+42>: popq   %rbp
    0x1000013bb <+43>: retq   
    0x1000013bc <+44>: nopl   (%rax)

以上看起来不是编译时间......?我对mac / lldb上的汇编程序还不熟悉。

1 个答案:

答案 0 :(得分:2)

此代码:

func getSizeOfInt64() -> Int {
    return MemoryLayout<Int64>.size
}

生成此程序集:

MyApp`getSizeOfInt64():
    0x1000015a0 <+0>:  pushq  %rbp
    0x1000015a1 <+1>:  movq   %rsp, %rbp
    0x1000015a4 <+4>:  movl   $0x8, %eax
    0x1000015a9 <+9>:  popq   %rbp
    0x1000015aa <+10>: retq  

由此看来,MemoryLayout<Int64>.size确实是编译时。

检查stridealignment的程序集是留给读者的,但它们会给出类似的结果(实际上相同,在Int64的情况下)。

编辑:

如果我们谈论泛型函数,显然还有更多的工作要做,因为函数不知道它在编译时获得大小的类型,因此不能只是放入一个常量。但是,如果你定义你的函数来采用类型而不是类型的实例,那么它的工作量比你的例子少一些:

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