Hacker News 中文摘要

RSS订阅

Ultrassembler为何如此快速? -- How is Ultrassembler so fast?

文章摘要

Ultrassembler是一个超快的RISC-V汇编器库,作为Chata信号处理项目的一部分开发。它直接在C++代码中调用,避免了使用外部二进制文件或系统命令的繁琐方式,从而提高了效率和集成性。

文章总结

Ultrassembler为何如此快速?

Ultrassembler是一个超快的RISC-V汇编器库,作为Chata信号处理项目的一部分开发。它的设计目标是速度和标准一致性,尤其是在嵌入式系统中,传统的汇编器如asllvm-mc由于需要通过外部调用运行,带来了显著的性能开销。Ultrassembler通过直接嵌入C++代码中,避免了这些开销。

性能优化

Ultrassembler在性能优化方面取得了显著成果。经过数月的优化,它能够以比as快10倍、比llvm-mc快20倍的速度处理包含约1.6万条RISC-V指令的测试文件。具体来说,Ultrassembler仅需约1000条CPU指令来汇编一条RISC-V指令,而asllvm-mc分别需要1万和2万条指令。

关键优化技术

  1. 异常处理:C++的异常处理通常被认为很慢,但在Ultrassembler中,异常处理是零开销的。只有在发生错误时才会触发异常,而这种情况在正常使用中极少发生,因此不会影响性能。

  2. 快速数据结构:Ultrassembler使用紧凑的数据结构来存储RISC-V指令和寄存器信息。通过避免使用字符串和巧妙的位掩码技术,Ultrassembler能够在仅20kB的内存中存储所有指令信息,极大地提高了数据访问速度。

  3. 预分配内存池:为了避免动态内存分配带来的性能开销,Ultrassembler使用了预分配的内存池。这种方法不仅减少了系统调用,还提高了内存局部性,进一步提升了性能。

  4. 值推测:通过提前加载下一个字符,Ultrassembler减少了字符串解析时的等待时间,这一优化使得整体性能提升了约10%。

  5. 智能搜索:Ultrassembler使用代码生成技术来生成高效的指令搜索代码,使得在数千条指令中进行查找时几乎没有任何性能开销。

  6. 编译时模板:通过使用C++的模板技术,Ultrassembler在编译时生成特定的代码路径,减少了运行时的计算开销。

  7. 快速字符串比较:Ultrassembler使用优化的字符串比较函数,通过提前检查字符串长度和使用C++20的[[likely]][[unlikely]]标签,进一步减少了比较操作的开销。

  8. 避免插入和删除操作:在处理跳转指令时,Ultrassembler通过使用占位符和后期修正的技术,避免了在汇编过程中进行插入和删除操作,从而提高了性能。

其他优化

  • 内存填充:预分配常用字符串的内存,减少了运行时内存分配的开销。
  • 内联函数:将小型函数标记为inline,减少了函数调用的开销。
  • 最小化字符串复制:通过优化字符串处理逻辑,减少了不必要的字符复制操作。
  • 去除编译垃圾:通过禁用不必要的编译器功能(如RTTI和堆栈保护器),减少了代码的运行时开销。
  • 链接时优化(LTO):启用LTO使得编译器能够在链接阶段进行更深入的优化,进一步提升了性能。
  • 内存友好的结构体:通过合理排列结构体成员,提高了内存访问效率。
  • 内存局部性:通过将频繁访问的数据放在一起,减少了内存访问的延迟。

结论

Ultrassembler通过一系列精心设计的优化技术,实现了极高的汇编速度。这些优化不仅适用于Ultrassembler,也可以应用于其他需要高性能的代码中。如果你对Ultrassembler感兴趣,可以在GitHub上查看其源代码:Ultrassembler

评论总结

  1. 关于汇编语言的实用性

    • 观点:汇编语言在大多数情况下并非性能瓶颈,LLVM和GAS已经非常高效。
    • 论据:RISC-V汇编的规范主要基于GCC/Clang的行为,可能更多作为参考。
    • 引用:
      • "Neat, but it's not like assembly is really a bottleneck in any but the most extreme cases."(“不错,但汇编语言在大多数情况下并不是性能瓶颈。”)
      • "I feel like this might mostly be useful as a reference, because currently RISC-V assembly's specification is mostly 'what do GCC/Clang do?'"(“我觉得这可能主要作为参考,因为目前RISC-V汇编的规范主要是‘GCC/Clang做了什么?’”)
  2. 关于作者互动

    • 观点:作者愿意回答读者问题,打破沉默。
    • 引用:
      • "Feel free to ask me any questions to break the radio silence!"(“欢迎提问,打破沉默!”)
  3. 关于优化技术

    • 观点:可以考虑使用完美哈希或其他工具(如flex)来优化比较树。
    • 论据:flex可以生成类似的树结构,但未与显式树进行性能对比。
    • 引用:
      • "I wonder if you thought about perfect hashing instead of that comparison tree."(“我想知道你是否考虑过使用完美哈希来代替比较树。”)
      • "flex (as in flex and bison) can generate what amounts to trees like that, I believe."(“我相信flex(如flex和bison)可以生成类似的树结构。”)
  4. 关于C++异常处理的性能

    • 观点:C++异常处理并非零开销,存在时间与空间的权衡。
    • 论据:G++在未捕获异常时选择空间而非时间优化。
    • 引用:
      • "Exceptions in C++ are never zero-overhead."(“C++中的异常处理从来不是零开销。”)
      • "There is a time-space tradeoff for performance of uncaught exceptions, and G++ picks space over time."(“未捕获异常的性能存在时间与空间的权衡,G++选择空间而非时间。”)
  5. 关于词法分析器的创新性

    • 观点:文章描述的词法分析器方法较为简单,缺乏创新。
    • 引用:
      • "Here's one weird trick I haven't seen anywhere else." … describes a simplistic lexer. Hmm.(“‘这里有一个我从未见过的奇怪技巧。’……描述了一个简单的词法分析器。嗯。”)
  6. 关于C++内存分配的误解

    • 观点:C++容器在几何增长时并不需要每次进行系统调用。
    • 论据:默认的glibc分配器会预先分配内存并重用已释放的内存。
    • 引用:
      • "That is not true - no allocator I know of (and certainly not the default glibc allocator) allocates memory in this way."(“这不是真的——我所知道的分配器(当然不是默认的glibc分配器)不会这样分配内存。”)
      • "It only does a syscall when it doesn’t have free userspace memory to hand out but it overallocates that memory and also reuses memory you’ve already freed."(“它只在没有空闲用户空间内存时进行系统调用,但它会预先分配内存并重用已释放的内存。”)