在进入鲲鹏环境后进行数据库压力测试时,会出现数据块不正确的情况
问题描述:
首先发现数据块的文件位置错了,比如日志“LoadBlock 数据块读取出现错误(Ret:0, DataFlag:1111769683),文件[/data/Vernox/Data/Vernox/TableFile.dbf],数据块[812516(599146)]的位置:9830858752”,我们可以发现需要读取的数据块编号是812516,但实际读出来的数据块编号是599146,而文件的位置是9830858752。我们数据块大小为16K,所以599146 * 16384=9816408064。所以最终确定读文件是没有问题的,问题出现在文件位置参数错了。
分析思路:
这种内存访问乱序是由于未使用CPU内存屏障导致的。
x86属于强内存序模型,仅会发生“写-读”乱序,即写操作后的读操作被乱序到写操作前执行。与x86不同,ARM64架构的CPU往往是弱内存序模型,除了依赖读操作,所有读/写操作都可能出现乱序的问题,这会影响无锁编程的代码,例如使用基于"原子操作+内存屏障"实现锁机制的代码。
解决方法:
处理方法是找到使用无锁编程的代码,检查是否使用内存屏障指令保证了数据的一致性。
编写的内存屏障的代码是:
_asm_ _volatile_("":::"memory");
在ar64平台需替换为:
_asm_ _volatile_("dmb ish" :::"memory");
[小科普]
什么是内存屏障?
由于现代CPU的运行速度往往要比内存要快得多——一般在从内存获取一个变量的同时,CPU可以执行数百条指令,因此现代计算机架构往往在CPU和内存之间增加一级缓存,以允许在缓存中快速访问较为频繁使用的数据。与此同时,CPU被设计成在从内存中获取数据的同时,可以执行其他指令和内存引用,这就导致了指令和内存引用的乱序执行。为了解决这一内存乱序问题,引入了各种同步原语,这些原语通过使用内存屏障来实现多处理器之间内存访问的顺序一致性。
什么时候需要使用内存屏障?
仅仅在两个CPU之间存在需要通过共享内存来实现交互的可能时,才需要使用内存屏障。
架构差异
在代码移植过程中,我们需要格外注意不同架构之间的内存序模型的差异。
CPU 内存屏障指令移植
CPU 内存屏障指令移植 在x86-64架构中,内存屏障指令主要分为sfence、lfence和mfence三类。
● sfence指令前后的写入(store/release)指令,按照在sfence前后的指令序进行执 行。从硬件上来说,保证store bñffr数据全部被清空的时候才继续往后面执行。
● lfence指令前后的读取(load/acquire)指令,按照在lfence前后的指令序进行执 行。
● mfence指令之前的写入(store/release)指令,都在该mfence指令之后的写入 (store/release)指令之前(指令序,Program Order)执行。既确保写者能够按 照指令序完成数据写入,也确保读者能够按照指令序完成数据读取。
例如,在x86上的代码段:
__asm__ __volatile__("sfence" : : : "memory");
__asm__ __volatile__("lfence" : : : "memory");
__asm__ __volatile__("mfence" : : : "memory");
在鲲鹏上分别替换为:
__asm__ __volatile__("dmb ishst" : : : "memory");
__asm__ __volatile__("dmb ishld" : : : "memory");
__asm__ __volatile__("dmb ish" : : : "memory")
在进入鲲鹏环境后进行数据库压力测试时,会出现数据块不正确的情况
问题描述:
首先发现数据块的文件位置错了,比如日志“LoadBlock 数据块读取出现错误(Ret:0, DataFlag:1111769683),文件[/data/Vernox/Data/Vernox/TableFile.dbf],数据块[812516(599146)]的位置:9830858752”,我们可以发现需要读取的数据块编号是812516,但实际读出来的数据块编号是599146,而文件的位置是9830858752。我们数据块大小为16K,所以599146 * 16384=9816408064。所以最终确定读文件是没有问题的,问题出现在文件位置参数错了。
分析思路:
这种内存访问乱序是由于未使用CPU内存屏障导致的。
x86属于强内存序模型,仅会发生“写-读”乱序,即写操作后的读操作被乱序到写操作前执行。与x86不同,ARM64架构的CPU往往是弱内存序模型,除了依赖读操作,所有读/写操作都可能出现乱序的问题,这会影响无锁编程的代码,例如使用基于"原子操作+内存屏障"实现锁机制的代码。
解决方法:
处理方法是找到使用无锁编程的代码,检查是否使用内存屏障指令保证了数据的一致性。
编写的内存屏障的代码是:
_asm_ _volatile_("":::"memory");
在ar64平台需替换为:
_asm_ _volatile_("dmb ish" :::"memory");
[小科普]
什么是内存屏障?
由于现代CPU的运行速度往往要比内存要快得多——一般在从内存获取一个变量的同时,CPU可以执行数百条指令,因此现代计算机架构往往在CPU和内存之间增加一级缓存,以允许在缓存中快速访问较为频繁使用的数据。与此同时,CPU被设计成在从内存中获取数据的同时,可以执行其他指令和内存引用,这就导致了指令和内存引用的乱序执行。为了解决这一内存乱序问题,引入了各种同步原语,这些原语通过使用内存屏障来实现多处理器之间内存访问的顺序一致性。
什么时候需要使用内存屏障?
仅仅在两个CPU之间存在需要通过共享内存来实现交互的可能时,才需要使用内存屏障。
架构差异
在代码移植过程中,我们需要格外注意不同架构之间的内存序模型的差异。
CPU 内存屏障指令移植
CPU 内存屏障指令移植 在x86-64架构中,内存屏障指令主要分为sfence、lfence和mfence三类。
● sfence指令前后的写入(store/release)指令,按照在sfence前后的指令序进行执 行。从硬件上来说,保证store bñffr数据全部被清空的时候才继续往后面执行。
● lfence指令前后的读取(load/acquire)指令,按照在lfence前后的指令序进行执 行。
● mfence指令之前的写入(store/release)指令,都在该mfence指令之后的写入 (store/release)指令之前(指令序,Program Order)执行。既确保写者能够按 照指令序完成数据写入,也确保读者能够按照指令序完成数据读取。
例如,在x86上的代码段:
__asm__ __volatile__("sfence" : : : "memory");
__asm__ __volatile__("lfence" : : : "memory");
__asm__ __volatile__("mfence" : : : "memory");
在鲲鹏上分别替换为:
__asm__ __volatile__("dmb ishst" : : : "memory");
__asm__ __volatile__("dmb ishld" : : : "memory");
__asm__ __volatile__("dmb ish" : : : "memory")