- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
你好,
因此,我正在优化我为正在开发的简单操作系统编写的一些函数。此函数 putpixel()
目前看起来像这样(以防我的程序集不清楚或错误):
uint32_t loc = (x*pixel_w)+(y*pitch);
vidmem[loc] = color & 255;
vidmem[loc+1] = (color >> 8) & 255;
vidmem[loc+2] = (color >> 16) & 255;
这需要一点解释。首先,loc
是我要写入显存中的像素索引。 X 和 Y 坐标传递给函数。然后,我们将 X 乘以以字节为单位的像素宽度(在本例中为 3),将 Y 乘以每行中的字节数。可以找到更多信息here .
vidmem
是一个全局变量,一个指向显存的uint8_t
指针。
话虽如此,任何熟悉位运算的人都应该能够相当轻松地弄清楚 putpixel()
的工作原理。
现在,这是我的程序集。请注意,它尚未经过测试,甚至可能会变慢或根本无法正常工作。这个问题是关于如何编译的。
我已经将 loc
定义之后的所有内容替换为:
__asm(
"push %%rdi;"
"push %%rbx;"
"mov %0, %%rdi;"
"lea %1, %%rbx;"
"add %%rbx, %%rdi;"
"pop %%rbx;"
"mov %2, %%rax;"
"stosb;"
"shr $8, %%rax;"
"stosb;"
"shr $8, %%rax;"
"stosb;"
"pop %%rdi;" : :
"r"(loc), "r"(vidmem), "r"(color)
);
当我编译它时,clang 会针对每个 push
指令给我这个错误:
因此,当我看到该错误时,我认为这与我遗漏了 GAS 后缀有关(无论如何,这应该是隐式决定的)。但是当我添加“l”后缀(我所有的变量都是uint32_t
s)时,我得到了同样的错误!我不太确定是什么原因造成的,我们将不胜感激。提前致谢!
最佳答案
通过将 vidmem
加载到存储之前的局部变量中,您可能可以使 C 版本的编译器输出更加高效。实际上,它不能假定存储不为 vidmem
设置别名,因此它会在每个字节存储之前重新加载指针。嗯,这确实让 gcc 4.9.2 避免重新加载 vidmem
,但它仍然会生成一些讨厌的代码。 clang 3.5 稍微好一些。
实现我在对您的回答的评论中所说的(stos
是 3 uops,而 mov
是 1):
#include <stdint.h>
extern uint8_t *vidmem;
void putpixel_asm_peter(uint32_t color, uint32_t loc)
{
// uint32_t loc = (x*pixel_w)+(y*pitch);
__asm( "\n"
"\t movb %b[col], (%[ptr])\n"
"\t shrl $8, %[col];\n"
"\t movw %w[col], 1(%[ptr]);\n"
: [col] "+r" (color), "=m" (vidmem[loc])
: [ptr] "r" (vidmem+loc)
:
);
}
编译成一个非常有效的实现:
gcc -O3 -S -o- putpixel.c 2>&1 | less # (with extra lines removed)
putpixel_asm_peter:
movl %esi, %esi
addq vidmem(%rip), %rsi
#APP
movb %dil, (%rsi)
shrl $8, %edi;
movw %di, 1(%rsi);
#NO_APP
ret
所有这些指令在 Intel CPU 上解码为单个 uop。 (存储可以微融合,因为它们使用单寄存器寻址模式。)movl %esi, %esi
将高位 32 归零,因为调用者可能已经生成了具有 64 位的函数 arg在 %rsi
的高 32 位指示剩余垃圾。您的版本可以通过首先使用约束来请求所需寄存器中的值来保存一些指令,但这仍然比 stos
另请注意我是如何让编译器负责将 loc
添加到 vidmem
的。您可以在自己的代码中更有效地完成它,使用 lea
将添加与移动结合起来。但是,如果编译器想在循环中使用它时变得聪明,它可以递增指针而不是递增地址。最后,这意味着相同的代码将适用于 32 位和 64 位。 %[ptr]
在 64 位模式下是 64 位 reg,但在 32 位模式下是 32 位 reg。因为我不需要对它做任何数学运算,所以它很管用。
我使用 =m
输出约束来告诉编译器我们在内存中写入的位置。 (我应该将指针转换到 struct { char a[3]; }
或其他东西,以告诉 gcc 它实际写入了多少内存,根据“Clobbers”末尾的提示the gcc manual 中的部分)
我还使用color
作为输入/输出约束来告诉编译器我们修改了它。如果它被内联,并且后面的代码预计仍会在寄存器中找到 color
的值,我们就会遇到问题。在函数中使用它意味着 color
已经是调用者值的 tmp 副本,因此编译器将知道它需要丢弃旧颜色。使用两个只读输入在循环中调用它可能会稍微更有效:一个用于 color
,一个用于 color >> 8
。
请注意,我可以将约束写为
: [col] "+r" (color), [memref] "=m" (vidmem[loc])
:
:
但是使用 %[memref]
和 1 %[memref]
生成所需的地址会导致 gcc 发出
movl %esi, %esi
movq vidmem(%rip), %rax
# APP
movb %edi, (%rax,%rsi)
shrl $8, %edi;
movw %edi, 1 (%rax,%rsi);
双寄存器寻址模式意味着存储指令不能微融合(至少在 Sandybridge 和更高版本上)。
void putpixel_cast(uint32_t color, uint32_t loc)
{
// uint32_t loc = (x*pixel_w)+(y*pitch);
typeof(vidmem) vmem = vidmem;
vmem[loc] = color & 255;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
*(uint16_t *)(vmem+loc+1) = color >> 8;
#else
vmem[loc+1] = (color >> 8) & 255; // gcc sucks at optimizing this for little endian :(
vmem[loc+2] = (color >> 16) & 255;
#endif
}
编译为(gcc 4.9.2 和 clang 3.5 给出相同的输出):
movq vidmem(%rip), %rax
movl %esi, %esi
movb %dil, (%rax,%rsi)
shrl $8, %edi
movw %di, 1(%rax,%rsi)
ret
这只比我们使用内联 asm 得到的效率低一点点,如果内联到循环中,优化器应该更容易优化。
在循环中调用它可能是一个错误。将多个像素组合在一个寄存器(尤其是 vector 寄存器)中,然后一次写入会更有效。或者,进行 4 字节写入,与前一次写入的最后一个字节重叠,直到到达末尾并且必须保留最后一个 block 3 之后的字节。
参见 http://agner.org/optimize/有关优化 C 和 asm 的更多信息。该链接和其他链接可在 https://stackoverflow.com/tags/x86/info 找到.
关于c - 内联汇编导致没有前缀的错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31689292/
我试图在图形模式下打印一个字符。通常当我打印我正在做的一个字符时: mov ah,14 ; ah=14 mov al,'x' int 10h ; print the character 这
我试图通过更改其中的一个字节来修改存储在内存中的字符串。我为此使用了 movb,但由于某种原因,给定内存位置的字节没有改变。 在 gdb 调试器上: 14 movb %al, (%r10) # nex
我一直在阅读一些汇编代码,并且开始发现调用指令实际上是与程序计数器相关的。 但是,每当我使用 Visual Studio 或 Windbg 进行调试时,它总是显示 call 0xFFFFFF ...这
我最近一直在使用 Visual C++ 中的内联汇编,我想知道是否可以直接向堆栈上的局部变量添加值,例如: push 5 add [esp], 7 这样做可以吗?我问这个问题是因为我在执行此操作时随机
我有下一个代码: mov al, -5 add al, 132 add al, 1 据我检查,溢出标志和进位标志将在第一个操作中设置,而在第二个操作中,仅设置溢出。 但我不明白为什么: 在无符号数中,
在 64 位 x86 汇编 nasm 中,如何将单个字节从寄存器移动到 .data 节中定义的内存位置? 我知道这有效 global _main section .data quotient db 0
我的汇编代码有问题。我想打印存储在寄存器 cx 中的数字,但是当我尝试打印它时,它打印的是 ascii 字符而不是 ascii 数字,所以我决定编写一个程序将 ascii char 转换为 ascii
为什么第 1B 行的跳转指令(例如)变成了 EBBD? 我知道“jmp”= EB但是BD是怎么计算的呢? 最佳答案 短跳转使用一个带符号的偏移量添加到 JMP 之后的指令地址。 例如,第一个 JMP
以下两者有什么区别: mov eax, [eax+4] 和 add eax, 4 mov eax, [eax] 如果不是,那么汇编器是否会选择哪个来进行某种优化? 最佳答案 这
看《The Shellcoder's Handbook》中的一些汇编和反汇编代码,发现一条指令的序列操作数是不一样的。 例如,在 assembly 上: mov ebx,0 并且,在反汇编时: mov
我有这个非常简单的汇编代码: start: add ax, 100 ; if ax overflow add to bx 1 jmp start 但我不知道如何检测 ax 寄存器溢出,有人可以帮
在 64 位 x86 汇编 nasm 中,如何将单个字节从寄存器移动到 .data 节中定义的内存位置? 我知道这有效 global _main section .data quotient db 0
我的汇编代码有问题。我想打印存储在寄存器 cx 中的数字,但是当我尝试打印它时,它打印的是 ascii 字符而不是 ascii 数字,所以我决定编写一个程序将 ascii char 转换为 ascii
我正在学习一些关于操作系统开发的教程,我发现了一篇关于多重引导 header 。这些是您必须定义的一些“神奇”值才能使用GRUB2。这些是命令: # Declare constants used f
为什么第 1B 行的跳转指令(例如)变成了 EBBD? 我知道“jmp”= EB但是BD是怎么计算的呢? 最佳答案 短跳转使用一个带符号的偏移量添加到 JMP 之后的指令地址。 例如,第一个 JMP
我正在尝试从内存中复制一些单词并使用汇编将其保存到另一个内存地址。我正在尝试为其编写代码,但我不确定其中的某些部分。我将简要描述我想要做什么。 源地址、目标地址和要复制的字数是函数的输入参数。 最佳答
当我们想要像这样创建一个初始化变量时: name db 'zara ali' 我们创建了一个字节大小变量,但我们在其中存储了一个字符串 这怎么可能?? 当我们使用这条指令时: MOV ecx, nam
我还是汇编的新手,我还不知道汇编中的许多命令代码。我想在 16 位寄存器中进行除法。我想打印它的内容。我知道我需要将寄存器的内容转换为 ASCII 进行打印,但同样,我的问题是除法。请帮我。 比如cx
使用有什么区别: c.eq.s $1, $2 bc1t L2 并使用: beq $1, $2, L2 如果他们做同样的事情,为什么有两种分支方式?如果它们不同,那么它们各自的好处是什么
源代码: int main() { int i; for(i=0, i : push rbp 2. 0x000055555555463b :
我是一名优秀的程序员,十分优秀!