get the function offset and jump to the function at runtime dynamically

Indeed, getting the function offset dynamically during runtime would require you to parse the symbol table of the binary. This is a complex task that would usually be done using a library like libelf or libbfd.

However, if you want a simpler solution that uses command-line tools, you can use nm or readelf to create a text file with the function offsets, and then read that file at runtime. Here’s how you might do it:

  1. Compile your program with debug symbols and without ASLR:

    1
    gcc -g -fno-pie -no-pie myprogram.c -o myprogram
  2. Create a text file with the function offsets:

    1
    nm myprogram | grep ' T ' > offsets.txt

    This command will create a text file called offsets.txt that contains the offsets of all the functions in myprogram. The ‘T’ indicates that we are interested in the text (code) section of the binary.

  3. Read the offsets file at runtime and create function pointers:

    Here’s an example in C that reads the offsets file and creates a function pointer for my_function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

void my_function() {
printf("Hello, world!\n");
}

int main() {
FILE *file = fopen("offsets.txt", "r");
if (file == NULL) {
printf("Cannot open offsets file!\n");
return 1;
}

uintptr_t offset = 0;
char name[256];

while (fscanf(file, "%lx %*s %255s", &offset, name) == 2) {
if (strcmp(name, "my_function") == 0) {
break;
}
}

fclose(file);

if (strcmp(name, "my_function") != 0) {
printf("Did not find my_function!\n");
return 1;
}

// Cast the offset to a function pointer
void (*func_ptr)() = (void (*)())offset;

// Call the function through the pointer
func_ptr();

return 0;
}

This program reads the offsets file, line by line, until it finds my_function. It then creates a function pointer using the offset and calls the function.

Note that this only works if your program is compiled without ASLR (hence the -no-pie flag to gcc). If ASLR is enabled, the offsets will be different every time the program is run. Also note that this example only works for function with no parameters, if your function has parameters, you need to adjust the function pointer type accordingly.

Please be aware that parsing the symbol table and manually creating function pointers like this is risky and not generally recommended. It can easily lead to crashes, undefined behavior, or security vulnerabilities if not done properly. Use it carefully and only if you understand exactly what you are doing.

dlsym

It’s indeed unusual and more complex to dynamically determine the offset and execute a function at runtime. For this, you would need to parse the binary’s symbol table at runtime. Here’s a rough idea of how you might do it using the dlfcn library’s dlsym function, which can find a function’s address in a dynamically linked executable at runtime:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <dlfcn.h>

void my_function() {
printf("Hello, world!\n");
}

int main() {
void* handle = dlopen(NULL, RTLD_LAZY); // Open the current executable
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
return 1;
}

dlerror(); // Clear any existing error

// Find the function in the executable's symbol table
void (*func_ptr)() = dlsym(handle, "my_function");

char* error = dlerror();
if (error) {
fprintf(stderr, "%s\n", error);
return 1;
}

// Call the function through the pointer
func_ptr();

dlclose(handle); // Close the executable

return 0;
}

This example uses the dlopen function to open the current executable (the NULL argument means to open the current process’s symbol table), the dlsym function to find the function in the symbol table, and a function pointer to call the function.

This approach requires that your executable is dynamically linked and the function is exposed in the symbol table, i.e., it’s not declared as static and not stripped out by the linker or strip tool. You may need to pass the -rdynamic option to the linker (usually via -Wl,-export-dynamic gcc option) to ensure that the symbol is included in the dynamic symbol table.

Again, manipulating function pointers like this can be dangerous and is generally not recommended unless you know what you are doing. The best way to call a function at runtime is typically to use a function pointer that is set at compile time.

ai os

ai os

assume

假设 ai 的能力得到年极大的发展,可以获取任意的知识,翻译任意文本,但是会消耗算力,这时候什么样的信息是有价值的

在AI能力大幅提升的假设下,能够获取任意知识、翻译任意文本的AI带来了极大的信息处理和获取便利。然而,由于其依赖算力,这种情况下的“有价值信息”可以从以下几个方面进行衡量:

  1. 独特性与稀缺性:那些难以获取、稀缺且独特的信息最为有价值。比如,一些专业领域的深度研究、尚未广泛公开的数据集或独有的行业经验。这类信息无法轻易通过现有的公共数据来源获取,因此更为珍贵。

  2. 实时性与时效性:在快速变化的环境中,及时掌握最新的发展动态和趋势信息尤为关键。例如,金融市场数据、科技前沿动态、政治形势变化等。这些信息对决策和预测具有高度影响。

  3. 复杂性与深度:对于复杂性较高的、多层次的信息,AI处理后能够提炼出深刻的见解或策略。此类信息涉及深度理解、跨领域关联性和推理能力,特别是在科学、工程、医疗等领域的高级知识。

  4. 隐私性与安全性:敏感的个人、商业或政府数据在严格的隐私和安全管控下具有高度价值。这类信息无法通过常规途径获取,因此在某些情况下可能极具战略性意义。

  5. 决策指导与预测能力:那些能够帮助个体或组织做出高效决策、提供可行建议或预测未来趋势的信息是极为有价值的。AI在处理大量数据后,能够提供基于数据的决策支持和长期预测方案。

  6. 创造性与创新性:与创新相关的构想、技术突破、以及新产品设计思路等,这些信息对于企业和学术界都有着不可替代的价值。AI如果能够从复杂的数据中提炼出新的模式或趋势,将极大促进创新。

  7. 整合与上下文理解:在信息过载的环境下,能够将分散的信息整合、并基于上下文提供深入解读的能力尤为重要。这类信息可以帮助决策者减少混乱、聚焦关键点,从而更好地应对复杂问题。

在算力成本成为考量因素的情况下,获取稀缺、实时、复杂、高影响力、具战略意义的信息将更具价值。

world as a system

如果将整个世界视为一个计算机或者信息系统,分别对应哪些层次?

如果将整个世界类比为一个计算机或信息系统,可以从不同的层次进行对比和映射。以下是一些关键层次的类比:

1. 物理层 (Hardware / Physical Layer)

  • 对应:物理世界
    在计算机系统中,物理层代表底层硬件、处理器、内存、电路等,负责信息的传输、存储和处理。与此对应的是自然界的物理法则、地球物理资源、生物体、能源等。比如,大自然中的物理定律(如重力、电磁力)可以类比为计算机中的硬件逻辑,资源(如矿产、空气、能源)则相当于硬件资源。

2. 系统层 (Operating System Layer)

  • 对应:社会基础设施与自然规则
    操作系统管理计算机硬件资源,为应用程序提供服务。类比到世界,社会的基础设施和自然法则(如交通、金融、教育系统,生态系统,法律制度)类似于操作系统,调度并管理资源(如能源、信息、金融流通),为人类活动和自然活动提供平台。政府、金融机构、法律体系等相当于操作系统的调度机制,确保整个“系统”的稳定运行。

3. 网络层 (Network / Communication Layer)

  • 对应:人际交流、全球通信网络
    计算机系统的网络层负责数据的传输与通信,确保信息在不同设备之间顺畅流动。对应到世界,通信层可以类比为人际交流、互联网、全球通讯网络、语言等,负责在不同社会群体、国家之间进行信息交换和传递。语言作为人类的交流方式,相当于计算机中的协议(如TCP/IP),它们使得不同群体能够进行高效交流。

4. 数据层 (Data Layer)

  • 对应:信息、知识与文化
    数据层在计算机系统中指的是数据存储和管理的部分。类比到世界,可以看作是人类积累的知识、信息、文化、历史等。图书馆、数据库、互联网存储的信息、科学研究的成果、历史记录等,都是这个层次的体现。类似于计算机系统中数据库的索引与查询机制,人类社会中也有教育体系、档案馆等机构用于存储和检索知识。

5. 应用层 (Application Layer)

  • 对应:人类活动、经济系统、技术产品
    应用层代表的是计算机系统中最终用户使用的程序或服务。对应到世界,这一层次可以理解为人类的各种活动、技术产品、经济系统等。人类通过技术和资源,创造出如生产系统、服务业、科学技术等,类似于应用程序为操作系统和硬件提供了实际的功能。公司的运营、个人的生活、经济运行等都属于这一层。

6. 用户界面层 (User Interface Layer)

  • 对应:感知与交互
    在计算机系统中,用户界面是人们与系统交互的窗口。类比到世界,用户界面可以看作是人类的感知系统(视觉、听觉等),以及与自然世界的交互方式,如社会结构、文化习惯、技术设备的操作方式等。我们通过感知世界的输入,类似于用户与软件或操作系统的交互。

7. 系统管理与控制层 (System Control / Management Layer)

  • 对应:决策系统与治理体系
    系统管理层包括操作系统的控制机制、权限管理、资源调度和健康监控。对应到世界,可以类比为人类的决策系统、政府管理体系和社会治理结构。这一层次负责监督世界各个层面的运行,调控资源的分配,确保系统正常运作。比如,国际组织、国家政府、企业管理层等就像操作系统中的管理模块,负责调控全局。

8. 算法与智能层 (Algorithm / Intelligence Layer)

  • 对应:思想、意识与人工智能
    计算机系统中的算法与智能层是系统做出决策、进行数据分析和处理的核心。类比到世界,思想、意识、逻辑推理、人工智能等就是这个层次的体现。人类的认知、思维、智能活动(包括AI技术的发展)相当于计算机中算法驱动的智能行为。

9. 安全与稳定层 (Security Layer)

  • 对应:法律与社会安全体系
    安全层负责计算机系统的保护,防止外部攻击或内部错误。对应到世界,这个层次可以类比为法律、道德、伦理体系,负责保护人类社会的安全与稳定,防止“系统”受到破坏或混乱。网络安全、金融监管、环境保护等方面也可以类比为系统的安全机制。

10. 演进与升级层 (Evolution / Upgrade Layer)

  • 对应:科技进步与社会演变
    计算机系统会随着时间不断演进与升级,增加新功能或提高性能。世界也在不断演化,科技进步、文化变迁、社会发展都在驱动世界向新的方向前进。这类似于计算机系统中操作系统的更新或应用的升级。

总结

通过这种类比,可以看到,世界和信息系统有许多相似之处,尤其是在资源管理、信息处理和系统演化等方面。理解这些类比有助于更好地把握复杂系统的运作原理,推动技术与社会的协调发展。

anything ai os

有哪些通用的概念可以扩展到真实世界里面呢,比如 cache,access control,尤其是假设 ai 的能力得到年极大的发展,可以获取任意的知识,翻译任意文本,但是会消耗算力

在AI能力大幅发展、可获取任意知识和翻译任意文本的假设下,许多计算机科学中的通用概念可以扩展到现实世界中。以下是一些能够在这种背景下扩展应用的概念:

1. 缓存 (Cache)

  • 概念:在计算机中,缓存用于存储频繁访问的数据,以加速数据的读取速度并减少重复计算。
  • 扩展应用:在现实世界中,缓存可以类比为短期记忆或知识库。AI可以缓存常用的知识、决策模式和预测结果,以减少重复计算和信息检索的消耗。比如,AI可以缓存某些常用的翻译模式、常见问题的解答,或快速响应的指令库,从而减少算力的消耗。这对于应对高频、重复性任务的场景非常有效。

2. 访问控制 (Access Control)

  • 概念:在信息系统中,访问控制用于管理谁有权访问哪些资源,以确保安全性和隐私保护。
  • 扩展应用:在现实世界中,AI能力极大发展的情况下,访问控制变得更加重要。例如,谁有权限调用AI系统来获取某些高度敏感的信息或进行深度决策?对信息和算力的访问权限需要严格管理,以防止滥用AI能力。访问控制也可以扩展到隐私保护领域,比如控制AI能够读取和处理哪些个人或组织的数据。

3. 负载均衡 (Load Balancing)

  • 概念:计算机系统通过负载均衡来将工作量分配给多个资源,以提高系统的效率和可靠性。
  • 扩展应用:在AI资源密集的环境中,负载均衡可以帮助优化算力的使用。现实世界的AI系统可以通过均衡不同任务的计算需求,合理分配算力,避免个别AI节点过载。这可以应用于多个AI系统协作的场景,比如分布式AI模型同时处理全球各地的不同请求,确保算力和资源的有效分配。

4. 带宽 (Bandwidth)

  • 概念:在计算机网络中,带宽表示数据传输的速度和容量。
  • 扩展应用:在现实世界中,带宽可以类比为AI系统处理和交互的容量限制。AI在接收和处理信息时,也会面临带宽限制的问题。如果AI接收到的数据量过大,带宽不足就会造成延迟或无法处理。为了提高效率,需要优化AI的数据输入输出通道,减少冗余数据的传输,提升带宽利用率。

5. 去重 (Deduplication)

  • 概念:去重用于消除数据中的冗余,确保只有唯一的副本存在,从而减少存储和处理负担。
  • 扩展应用:在知识获取和翻译任务中,去重的思想可以帮助减少重复的知识或冗余的信息处理。AI可以去除已经存储或掌握的重复知识,从而节省算力和存储空间。这可以在AI对大规模文本进行翻译或知识检索时减少不必要的计算量。

6. 事务处理 (Transaction Processing)

  • 概念:事务处理确保一系列操作要么全部成功,要么全部失败,以确保数据的一致性和完整性。
  • 扩展应用:在AI系统中,特别是多任务并发环境下,事务处理可以用于保证一系列复杂决策的连贯性。如果AI需要进行多个步骤的知识获取、翻译和推理,事务处理可以确保这些操作在逻辑上是一致的,并且在发生错误时可以回滚到稳定状态。这在处理敏感数据和复杂系统决策时尤为重要。

7. 指数退避算法 (Exponential Backoff)

  • 概念:在网络协议中,指数退避用于减少冲突,逐步延长重试请求的间隔时间。
  • 扩展应用:在AI系统中,指数退避可以用于管理资源访问冲突。当多个系统或用户同时请求AI资源时,使用指数退避可以帮助AI系统控制重试频率,避免资源争用导致的效率下降。这可以扩展到AI与人类或其他AI系统之间的协作场景,确保资源争夺时的平稳运行。

8. 异步处理 (Asynchronous Processing)

  • 概念:异步处理用于处理多个任务而不需要等待前一个任务完成,从而提高效率。
  • 扩展应用:在现实世界中,AI可以通过异步处理应对多个请求或任务。例如,AI可以一边处理复杂的翻译任务,一边同时处理实时数据分析。异步处理可以减少算力消耗的峰值,优化AI系统的响应时间,提升多任务处理的效率。

9. 垃圾回收 (Garbage Collection)

  • 概念:垃圾回收用于自动释放不再使用的内存,以避免系统资源的浪费。
  • 扩展应用:在AI的现实应用中,类似的机制可以用于清理不再需要的数据或推理结果。AI可以定期检查并删除不再使用的缓存、推理模型或临时数据,从而释放算力和存储资源。这对于长期运行的AI系统尤其重要,可以避免资源浪费并提高系统的持久性。

10. 压缩 (Compression)

  • 概念:压缩用于减少数据的存储体积,以优化传输和存储效率。
  • 扩展应用:在AI知识获取的过程中,信息压缩技术可以用于减少无关或冗余的细节,保留最核心的知识。这可以减少对算力的需求,尤其是在处理大量文本数据时,能够将信息提炼成更高效的表征,提升处理速度和响应效率。

11. 推测执行 (Speculative Execution)

  • 概念:推测执行用于预判未来可能的操作路径,提前执行计算,以提高处理效率。
  • 扩展应用:在AI系统中,推测执行可以用于预判用户需求或未来可能的知识查询。例如,AI可以提前加载和推理与某一领域相关的知识,以备用户后续可能的查询需求。这可以显著减少响应时间,提升用户体验。

12. 热路径 (Hot Path)

  • 概念:在软件工程中,热路径是被频繁访问的代码路径,优化热路径能够显著提升性能。
  • 扩展应用:在现实世界中,AI系统可以优化那些频繁调用的知识领域或任务路径。通过识别常用的任务或知识点并进行优化,AI可以将算力集中在这些“热路径”上,减少不常用任务的资源消耗,从而提升系统整体效率。

13. 优先级调度 (Priority Scheduling)

  • 概念:优先级调度用于根据任务的重要性分配资源,确保关键任务优先执行。
  • 扩展应用:AI系统可以根据任务的紧急性和重要性调整算力分配。例如,紧急的实时翻译请求可以优先分配资源,而非紧急的批量处理任务则可以延后处理。这种优先级调度可以帮助AI更好地分配有限的算力,提高关键任务的响应速度。

总结

这些通用概念在AI能力极大发展的情况下,能够有效应对算力和资源消耗的挑战,并帮助AI系统优化知识获取、翻译和处理任务。通过合理的资源调度、缓存、去重、压缩等机制,AI系统可以在满足实时性和复杂性要求的同时,最大限度减少算力的浪费,提升整体效率。

SIMD 加速:AVX2 指令集实现大小端转换

在应用 thrift 进行 RPC 通信的时候,由于 Thrift 采用了大端序,而 x86_64 等常见的处理器架构均为小端序,因此对于 list 这一类的数据类型需要一个循环来实现小端到大端的转换。而这个过程如果能够利用 SIMD 指令的话,可以极大的提高性能。这篇文章是在探索实现 Thrift 编译后端 Auto-vectorization Pass 的时候进行的一个尝试和学习,使用 avx2 指令集实现了一个简单的大小端转换的功能,并且对比了在不同条件下的加速性能。

原理

大小端转换

计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。

  • 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
  • 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

例如,对于内存中存放的数0x12345678来说

  • 如果是采用大端模式存放的,则其真实的数是:0x12345678
  • 如果是采用小端模式存放的,则其真实的数是:0x78563412

可以使用如下 API 进行转换:

1
2
3
4
5
6
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

也可以直接使用移位进行实现

1
2
3
4
5
6
7
8
inline uint32_t Swap32(uint32_t x)
{
return (
((x & 0x000000FF) << 24) |
((x & 0x0000FF00) << 8) |
((x & 0x00FF0000) >> 8) |
((x & 0xFF000000) >> 24));
}

bswap

大部分编译器同时提供了 bswap 指令,来帮助实现这一转换过程,例如在 gcc 中,我们可以使用 __builtin_bswap{16,32,64}

1
2
3
4
inline uint32_t Swap32(uint32_t x)
{
return __builtin_bswap32(x);
}

这是一个编译器的内置函数。在 x86_64 机器上,它会被编译为这样的指令序列:

1
2
3
4
Swap32(unsigned int):
mov eax, edi
bswap eax
ret

在 arm 机器上,它会被编译为这样的指令序列:

1
2
3
Swap32(unsigned int):
rev w0, w0
ret

通常来说,我们自己使用的移位函数实现的大小端转换,在编译器优化 O2 时也会被自动识别替换为 bswap 指令。

avx2 指令集

使用 SIMD 对于这样可以高度并行化的计算应该是一个更快的选择。bswap指令可以反转 2, 4, 或 8 字节的字节顺序,但 x86 中的 SIMD 扩展允许仅使用一条指令对多条数据通道进行并行操作。就像原子地反转寄存器中的所有四个字节一样,它提供了一个完整的算术指令集,允许使用一条指令同时并行处理多个数据实例。这些被操作的数据块往往被称为 vectors。一般来说可用的有如下几种 SIMD 指令集:

  • MMX (1996)
  • SSE (1999)
  • SSE2 (2001)
  • SSE3 (2004)
  • SSSE3 (2006)
  • SSE4 a/1/2 (2006)
  • AVX (2008)
  • AVX2 (2013)
  • AVX512 (2015)

目前较为常用的是 avx/avx2 指令集,早先的某些指令集主要是为了兼容性而保留的。具体的指令信息,可以参考 Intel 指令集查询

我们这里主要使用的是 _mm256_shuffle_epi8 指令,在 C 中它被定义在了 #include <immintrin.h> 头文件中。它实现了一个 vector 中字节的重排序,例如将一个 128 位的字节序列完全反转:

1
2
3
4
5
6
7
8
9
10
const __m256i ShuffleRev = _mm256_set_epi8(
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, // first 128-bit lane
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 // second 128-bit lane
);
// Load 32 elements at once into one 32-byte register
__m256i x = _mm256_loadu_si256(
reinterpret_cast<__m256i*>(&Array8[i])
);
// Reverse each the bytes in each 128-bit lane
x = _mm256_shuffle_epi8(x,ShuffleRev);

它接受一个ShuffleRev,定义具体每个字节应该被放到哪个位置。注意每128位为一个通道,重排范围只能在128位内,不能将前128位的内容重排至后128位。可以参照下图,可能会比较直观:

pshufb

来源:https://www.officedaytime.com/simd512e/simdimg/si.php?f=pshufb

在 gcc -O3 中,Auto-vectorization Pass 可以帮助我们自动识别可以被向量化的循环,并且使用 avx 指令集进行并行化优化。

avx2 code

这是一个对于 64 位整数的大小端转换 load-swap-store 循环,使用 avx2 指令集进行加速的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void reverse64_avx2(void *Array, size_t Count)
{
uint64_t *Array64 = (uint64_t *)(Array);
size_t i = 0;
const __m256i ShuffleRev = _mm256_set_epi8(
8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7);

for (; i < (Count & ~0b11); i += 4)
{
// Load 4 elements
__m256i bytes = _mm256_loadu_si256(
(__m256i *)(&Array64[i]));

// Reverse the byte order of our 64-byte vectors
bytes = _mm256_shuffle_epi8(bytes, ShuffleRev);

// Place them back into the array
_mm256_storeu_si256(
(__m256i *)(&Array64[i]),
bytes);
}

// Naive swaps for leftover elements
for (; i < Count; ++i)
{
Array64[i] = Swap64(Array64[i]);
}
}

avx2 指令集的向量是 256 位长度,相当于 4 个 64bit 的整数。由于输入的数组并不一定被 4 整除,因此结尾的部分使用一般转换法逐个进行大小端转换。

benchmark

测试环境:

  • Linux ubuntu 5.13.0-51-generic #58~20.04.1-Ubuntu SMP Tue Jun 14 11:29:12 UTC 2022 x86_64
  • Intel Core i7-10750H

编译指令:

1
gcc main.c  -mavx2 -fno-tree-vectorize -O3 -o avx 

basic 对照函数(这里 Swap64 会被 gcc 自动编译为 bswap 指令):

1
2
3
4
5
6
7
8
9
10
11
void reverse64_basic(void *Array, size_t Count)
{
uint64_t *Array64 = (uint64_t *)(Array);
size_t i = 0;

// Naive swaps
for (; i < Count; ++i)
{
Array64[i] = Swap64(Array64[i]);
}
}

我们分别对 64/32/16 位的整数进行大小端转换,并测试 bswap 和 avx2 的加速比:

array size avx2 64bit basic 64bit avx2 32bit basic 32bit avx2 16bit basic 16bit
4 2ns 3ns 3ns 3ns 4ns 2ns
8 3ns 4ns 2ns 4ns 15ns 9ns
16 5ns 9ns 3ns 9ns 3ns 10ns
32 9ns 37ns 5ns 18ns 4ns 19ns
64 19ns 34ns 9ns 34ns 6ns 59ns
128 35ns 181ns 15ns 76ns 9ns 82ns
256 52ns 120ns 26ns 477ns 11ns 712ns
512 86ns 248ns 44ns 192ns 29ns 254ns
1024 179ns 510ns 96ns 422ns 47ns 486ns
2048 383ns 996ns 179ns 812ns 96ns 981ns
4096 726ns 2190ns 457ns 2675ns 384ns 1878ns
8192 1544ns 4170ns 748ns 3434ns 401ns 4511ns
16384 3570ns 8977ns 1704ns 6771ns 793ns 7941ns

可以注意到,对于宽度更小的整数的数组,并行度更高,avx2 加速比更加明显。在 64 位时,加速比约为 2.5,在 16 位时,加速比已经达到了 10 倍。

生成的汇编

objdump -d ./avx > dump.s

我们可以再检查一下生成的汇编指令:

  • 使用 bswap 的大小端转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    0000000000001c30 <reverse64_basic>:
    1c30: f3 0f 1e fa endbr64
    1c34: 48 85 f6 test %rsi,%rsi
    1c37: 74 1a je 1c53 <reverse64_basic+0x23>
    1c39: 48 8d 14 f7 lea (%rdi,%rsi,8),%rdx
    1c3d: 0f 1f 00 nopl (%rax)
    1c40: 48 8b 07 mov (%rdi),%rax
    1c43: 48 83 c7 08 add $0x8,%rdi
    1c47: 48 0f c8 bswap %rax
    1c4a: 48 89 47 f8 mov %rax,-0x8(%rdi)
    1c4e: 48 39 d7 cmp %rdx,%rdi
    1c51: 75 ed jne 1c40 <reverse64_basic+0x10>
    1c53: c3 retq
    1c54: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
  • avx2:vpshufb

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    0000000000001bb0 <reverse64_avx2>:
    1bb0: f3 0f 1e fa endbr64
    1bb4: 48 89 f2 mov %rsi,%rdx
    1bb7: 48 83 e2 fc and $0xfffffffffffffffc,%rdx
    1bbb: 74 46 je 1c03 <reverse64_avx2+0x53>
    1bbd: c5 fd 6f 0d fb 14 00 vmovdqa 0x14fb(%rip),%ymm1 # 30c0 <_IO_stdin_used+0xc0>
    1bc4: 00
    1bc5: 48 8d 4a ff lea -0x1(%rdx),%rcx
    1bc9: 48 89 f8 mov %rdi,%rax
    1bcc: 48 8d 14 d7 lea (%rdi,%rdx,8),%rdx
    1bd0: c5 fa 6f 10 vmovdqu (%rax),%xmm2
    1bd4: c4 e3 6d 38 40 10 01 vinserti128 $0x1,0x10(%rax),%ymm2,%ymm0
    1bdb: 48 83 c0 20 add $0x20,%rax
    1bdf: c4 e2 7d 00 c1 vpshufb %ymm1,%ymm0,%ymm0
    1be4: c5 fa 7f 40 e0 vmovdqu %xmm0,-0x20(%rax)
    1be9: c4 e3 7d 39 40 f0 01 vextracti128 $0x1,%ymm0,-0x10(%rax)
    1bf0: 48 39 d0 cmp %rdx,%rax
    1bf3: 75 db jne 1bd0 <reverse64_avx2+0x20>
    1bf5: 48 83 e1 fc and $0xfffffffffffffffc,%rcx
    1bf9: 48 89 ca mov %rcx,%rdx
    1bfc: 48 83 c2 04 add $0x4,%rdx
    1c00: c5 f8 77 vzeroupper
    1c03: 48 39 d6 cmp %rdx,%rsi
    1c06: 76 1b jbe 1c23 <reverse64_avx2+0x73>
    1c08: 48 8d 04 d7 lea (%rdi,%rdx,8),%rax
    1c0c: 48 8d 0c f7 lea (%rdi,%rsi,8),%rcx
    1c10: 48 8b 10 mov (%rax),%rdx
    1c13: 48 83 c0 08 add $0x8,%rax
    1c17: 48 0f ca bswap %rdx
    1c1a: 48 89 50 f8 mov %rdx,-0x8(%rax)
    1c1e: 48 39 c1 cmp %rax,%rcx
    1c21: 75 ed jne 1c10 <reverse64_avx2+0x60>
    1c23: c3 retq
    1c24: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)

完整源代码,包含性能测试

参考资料

2022 一个新的开始

一点没啥意义的话

去年因为某些原因站点挂了,也没来得及备份…(好像是我躺在医院的床上晕晕乎乎的那段时间?)虽然大多数文章都散落在各个仓库或者网站上存着,倒也没损失什么。

无论如何,2022年都算是一个全新的开始吧。虽然日常已经是摆的不能再摆了,但总归还是有很多想要去追寻的东西。既然旧的事情离开的了无痕迹,那也没有更多需要回首的意义了。

但行好事,莫问前程。

Read more