<linux Assembly> atom_inc

本文探讨了原子操作在现代多核处理器系统中的作用及其对CPU性能的影响,详细介绍了几种常用的原子指令,如XCHG、BSWAP、XADD及CMPXCHG,并通过实例展示了如何利用这些指令实现高效的并发控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当然首先还是说 一下,好像在RISC 思想中, 使用 原子交换好像并不能让 CPU在处理流程上变得更快更高效. 

反而浪费了CPU的处理能力, 下面是转载

=================================================================== 

但是随着计算机硬件的快速发展,获得这种锁的开销相对于CPU的速度在成倍地增加,原因很简单,CPU的速度与访问内存的速度差距越来越大,而这种锁使用了原子操作指令,它需要原子地访问内存,也就说获得锁的开销与访存速度相关,另外在大部分非x86架构上获取锁使用了内存栅(Memory Barrier),这会导致处理器流水线停滞或刷新,因此它的开销相对于CPU速度而言就越来越大。表1数据证明了这一点。
 

表1是在700MHz的奔腾III机器上的基本操作的开销,在该机器上一个时钟周期能够执行两条整数指令。在1.8GHz的奔腾4机器上, 原子加1指令的开销要比700MHz的奔腾III机器慢75纳秒(ns),尽管CPU速度快两倍多。

这种锁机制的另一个问题在于其可扩展性,在多处理器系统上,可扩展性非常重要,否则根本无法发挥其性能。图1表明了Linux上各种锁的扩展性。

===================================================================

 

 

怎么说呢  ,至少我现在用 phtread_spin_lock 还是比较多的 , 我也看了glib 的实现 确实是是上面说的那种 ,

 

但是我同样坚持能不用锁 的地方就不用 粒度按照事件概率改造锁  (坚决不用pthread rw锁 等),或者和内核一样退化锁 为  RCU

 

 

3 常用指令

 

3.1 数据交换指令

 

为什么要提着几个呢 因为他能实现原子操作

普通的交换 或者相加,比如:

movl %eax,%ecx

movl %ebx,%eax

movl %ecx,%ebx

 

CPU 随时可能被讨厌的总线信号中断 ,或许你可以 屏蔽,但是效率不高,于是

 

XCHG :   两个寄存器之间 或寄存器与内存之间

              此时处理器被自动LOCK 防止SMP上其他的处理器访问

 

BSWAP: 交换32位寄存器中的字节序(little-endian <=> big-endian)

XADD :   交换两个值把综合存储在目标操作数中

 

CMPXCHG :把一个值和一个外部值进行比较并且交换他和另一个值(重要)

 比较目标操作数和EAX 寄存器中的值,

1 相等 : 就把源操作数的值加载到目标操作数中

2 不等 : 把目标操作数加载到EAX中

 

cmpxchg sour ,dest

 

比如你想实现下面的功能 把111 弄到数组最后一个

 

 my @arr = qw/ 111 32 9 22/;
 my $index = 0;
 foreach(@arr){
  if($arr[$index] ge $arr[index+1]){
    ($arr[$index] , $arr[index+1]) = ($arr[$index+1] , $arr[index]) ;
  $index++;
  .....
}}

 

movl (%esi) , %eax  ! esi 为数组arr首地址
cmp %eax , 4(%esi)  !相当于上面第4行
jgp leav
xchg %eax , 4(%esi)  !体会一下
movl %eax ,(%esi)     !
leav

 

 

 

其实 NPTL的线程库中 就是指令来实现 原子的交换,看看他怎么写的,学习学习

宏:  atomic_exchange_acq

#define atomic_exchange_acq(mem, newvalue) \
  ({ __typeof (*mem) result;						      \
     if (sizeof (*mem) == 1)						      \
       __asm __volatile ("xchgb %b0, %1"				      \
			 : "=q" (result), "=m" (*mem)			      \
			 : "0" (newvalue), "m" (*mem));			      \
     else if (sizeof (*mem) == 2)					      \
       __asm __volatile ("xchgw %w0, %1"				      \
			 : "=r" (result), "=m" (*mem)			      \
			 : "0" (newvalue), "m" (*mem));			      \
     else if (sizeof (*mem) == 4)					      \
       __asm __volatile ("xchgl %0, %1"					      \
			 : "=r" (result), "=m" (*mem)			      \
			 : "0" (newvalue), "m" (*mem));			      \
     else								      \
       __asm __volatile ("xchgq %q0, %1"				      \
			 : "=r" (result), "=m" (*mem)			      \
			 : "0" ((long) (newvalue)), "m" (*mem));	      \
     result; })

 很简单吧 先判断类型然后 xchg? 嘿嘿   至于怎么一路走过来的,就自己用vim 跟吧

 

同时也发现一个更强大的指令

 

# define __arch_compare_and_exchange_val_32_acq(mem, newval, oldval) \
  ({ __typeof (*mem) ret;            \
     __asm __volatile (LOCK_PREFIX "cmpxchgl %2, %1"         \
         : "=a" (ret), "=m" (*mem)         \
         : "r" (newval), "m" (*mem), "0" (oldval));       \
     ret; })

 

 类似的在用 gcc -O3 优化选项后  会用 一个指令  cmovl   %edx, %eax   

 

  代替 比较 跳转 赋值  ~

 

  当然对于用这么高的级别优化,最好了解每个优化子选项的意义. 不了解的话, 我个人感觉最好多用用__volatile__ 不然发生了死循环就不好了....

 

 

然后我们在来看看 nginx 里面的 原子操作

#if (NGX_SMP)
#define NGX_SMP_LOCK  "lock;"
#else
#define NGX_SMP_LOCK
#endif


/*
 * "cmpxchgl  r, [m]":
 *
 *     if (eax == [m]) {
 *         zf = 1;
 *         [m] = r;
 *     } else {
 *         zf = 0;
 *         eax = [m];
 *     }
 *
 *
 * The "r" means the general register.
 * The "=a" and "a" are the %eax register.
 * Although we can return result in any register, we use "a" because it is
 * used in cmpxchgl anyway.  The result is actually in %al but not in %eax,
 * however, as the code is inlined gcc can test %al as well as %eax,
 * and icc adds "movzbl %al, %eax" by itself.
 *
 * The "cc" means that flags were changed.
 */

static ngx_inline ngx_atomic_uint_t
ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
    ngx_atomic_uint_t set)
{
    u_char  res;

    __asm__ volatile (

         NGX_SMP_LOCK
    "    cmpxchgl  %3, %1;   "
    "    sete      %0;       "

    : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");

    return res;
}


/*
 * "xaddl  r, [m]":
 *
 *     temp = [m];
 *     [m] += r;
 *     r = temp;
 *
 *
 * The "+r" means the general register.
 * The "cc" means that flags were changed.
 */


#if !(( __GNUC__ == 2 && __GNUC_MINOR__ <= 7 ) || ( __INTEL_COMPILER >= 800 ))

/*
 * icc 8.1 and 9.0 compile broken code with -march=pentium4 option:
 * ngx_atomic_fetch_add() always return the input "add" value,
 * so we use the gcc 2.7 version.
 *
 * icc 8.1 and 9.0 with -march=pentiumpro option or icc 7.1 compile
 * correct code.
 */

static ngx_inline ngx_atomic_int_t
ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
{
    __asm__ volatile (

         NGX_SMP_LOCK
    "    xaddl  %0, %1;   "

    : "+r" (add) : "m" (*value) : "cc", "memory");

    return add;
}


#else

/*
 * gcc 2.7 does not support "+r", so we have to use the fixed
 * %eax ("=a" and "a") and this adds two superfluous instructions in the end
 * of code, something like this: "mov %eax, %edx / mov %edx, %eax".
 */

static ngx_inline ngx_atomic_int_t
ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
{
    ngx_atomic_uint_t  old;

    __asm__ volatile (

         NGX_SMP_LOCK
    "    xaddl  %2, %1;   "

    : "=a" (old) : "m" (*value), "a" (add) : "cc", "memory");

    return old;
}

#endif


/*
 * on x86 the write operations go in a program order, so we need only
 * to disable the gcc reorder optimizations
 */

#define ngx_memory_barrier()    __asm__ volatile ("" ::: "memory")

/* old "as" does not support "pause" opcode */
#define ngx_cpu_pause()         __asm__ (".byte 0xf3, 0x90")

 

如果你不想代码里面嵌入式 assembly 也可以用gcc 4 提供的特性

 

 

http://gcc.gnu.org/onlinedocs/gcc-4.5.2/gcc/Atomic-Builtins.html#Atomic-Builtins 写道
type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)
  These builtins perform the operation suggested by the name, and returns the value that had previously been in memory. That is,
          { tmp = *ptr; *ptr op= value; return tmp; }
          { tmp = *ptr; *ptr = ~(tmp & value); return tmp; }   // nand
     
<think>根据用户提供的父POM,需要将两个子模块(Employee_System和Prime_Count)打包成一个整体包(如可执行JAR或ZIP)。这里提供两种修改POM的方式:使用Maven Shade Plugin打包成单一可执行JAR,或使用Maven Assembly Plugin打包成包含所有模块JAR的ZIP。 ### 方案一:使用Maven Shade Plugin打包成单一可执行JAR(Uber-JAR) 修改父POM,在`<build>`标签内添加插件配置。注意:由于父POM的打包类型为`pom`,我们需要在父POM中配置插件,但实际构建会作用在子模块上。这里建议将Shade插件配置在需要作为主入口的子模块中(例如包含main方法的模块)。如果两个模块都需要合并,可以指定其中一个模块作为主模块,然后通过`<artifactSet>`包含另一个模块。 但更常见的做法是:在父POM中管理插件配置,然后在特定子模块中启用Shade插件。这里为了简化,假设`Employee_System`模块包含主类,我们将在该模块中配置Shade插件,同时包含另一个模块的代码。 不过,用户要求修改父POM,我们可以尝试在父POM中配置,但注意:父POM不能直接构建出JAR,所以我们需要在父POM中配置插件,但通过`<pluginManagement>`管理,然后在某个子模块中激活。 另一种做法:在父POM中直接配置Shade插件,并指定主类,然后通过`<artifactSet>`包含两个子模块。但这样构建出来的JAR会包含所有子模块的代码,但需要指定主类。 **修改父POM的步骤**: 1. 在父POM的`<build>`标签内添加Shade插件配置(注意:由于父POM的打包类型是pom,所以需要确保在子模块中执行打包时,这个插件能够被继承并执行。但是,通常我们会在父POM中通过`<pluginManagement>`定义,然后在某个子模块中引用并配置主类)。 2. 这里我们选择在父POM中直接配置插件(不在`<pluginManagement>`中),并指定一个主类(需要替换为实际的主类全限定名)。同时,通过`<artifactSet>`包含两个子模块。 修改后的父POM(部分): ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <!-- 替换成实际主类,例如:com.example.Main --> <mainClass>your.main.Class</mainClass> </transformer> </transformers> <artifactSet> <includes> <!-- 包含当前项目中的两个子模块,使用groupId:artifactId --> <include>${project.groupId}:Employee_System</include> <include>${project.groupId}:Prime_Count</include> </includes> </artifactSet> </configuration> </execution> </executions> </plugin> </plugins> </build> ``` 但是,请注意:这个配置在父POM中,当执行`mvn package`时,由于父POM的打包类型是`pom`,它不会生成JAR,而子模块会执行这个插件。但是,每个子模块都会执行这个插件,这可能会导致重复打包和构建失败。 因此,更推荐的做法是:在包含主类的子模块(例如`Employee_System`)中配置Shade插件,并在该模块的POM中指定要包含的其他模块。 ### 方案二:使用Maven Assembly Plugin打包成ZIP 在父POM中配置Assembly插件,创建一个ZIP包,包含所有子模块的JAR文件以及父POM。 **修改父POM**: 1. 在父POM的`<build>`标签内添加Assembly插件配置。 2. 创建一个自定义的`assembly.xml`描述文件(放在`src/assembly`目录下)。 修改后的父POM(部分): ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.5.0</version> <configuration> <descriptors> <descriptor>src/assembly/distribution.xml</descriptor> </descriptors> <outputDirectory>target</outputDirectory> <appendAssemblyId>false</appendAssemblyId> <finalName>${project.artifactId}-${project.version}</finalName> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` 创建`src/assembly/distribution.xml`文件: ```xml <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> <id>distribution</id> <formats> <format>zip</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <moduleSets> <moduleSet> <useAllReactorProjects>true</useAllReactorProjects> <binaries> <outputDirectory>lib</outputDirectory> <includeDependencies>false</includeDependencies> </binaries> </moduleSet> </moduleSets> <files> <file> <source>pom.xml</source> <outputDirectory>/</outputDirectory> </file> </files> </assembly> ``` ### 打包命令 执行: ```bash mvn clean package ``` - 方案一(Shade)的输出:在包含主类的子模块的`target`目录下生成一个可执行JAR(包含所有依赖和代码)。 - 方案二(Assembly)的输出:在父项目的`target`目录下生成一个ZIP包,包含所有子模块的JAR和父POM。 ### 建议 - 如果目标是生成一个可执行的应用程序,推荐使用方案一(在包含主类的子模块中配置Shade插件)。 - 如果目标是分发所有模块的JAR文件(比如库文件),则使用方案二。 §§相关问题§§ 1. 在父POM中配置Shade插件时,如何避免子模块重复执行插件导致构建失败? 2. 使用Assembly Plugin打包ZIP时,如何将外部依赖的JAR也包含到lib目录中? 3. 如果两个子模块都有主类,如何配置Shade Plugin避免冲突? 4. 如何为Assembly Plugin生成的ZIP包添加自定义目录结构(如配置文件目录)? 5. 在方案二中,如何排除某个子模块不被打包进ZIP?</think>根据您的父 POM 文件,要实现将两个子模块打包成单一整体包,推荐使用 **Maven Assembly Plugin**。以下是完整的 POM 修改方案和操作步骤: ### 修改后的父 POM 文件 ```xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>HomeWork_Java_Coding</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>Employee_System</module> <module>Prime_Count</module> </modules> <packaging>pom</packaging> <name>HomeWork_Java_Coding</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 添加主类属性(根据实际修改) --> <main.class>com.example.MainApp</main.class> </properties> <dependencies> <!-- 原有依赖保持不变 --> </dependencies> <build> <plugins> <!-- 添加 Assembly 插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.5.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>${main.class}</mainClass> </manifest> </archive> <appendAssemblyId>false</appendAssemblyId> <finalName>${project.artifactId}-bundle</finalName> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <!-- 确保子模块正确编译 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>17</source> <target>17</target> </configuration> </plugin> </plugins> </build> </project> ``` ### 关键修改说明 1. **添加 Assembly 插件配置**: - 使用 `jar-with-dependencies` 描述符自动包含所有依赖 - 设置统一的主类入口 `${main.class}`(需替换为实际主类) - 指定最终打包名为 `HomeWork_Java_Coding-bundle.jar` 2. **添加编译器插件**: - 明确指定 Java 17 版本(根据实际需求调整) 3. **属性配置**: - 添加 `main.class` 属性指定可执行主类 ### 打包操作步骤 1. **确保项目结构正确**: ``` HomeWork_Java_Coding (父项目) ├── pom.xml ├── Employee_System (子模块) │ └── pom.xml └── Prime_Count (子模块) └── pom.xml ``` 2. **在父项目目录执行命令**: ```bash mvn clean package ``` 3. **获取打包结果**: - 打包文件位置:`target/HomeWork_Java_Coding-bundle.jar` - 包含所有子模块代码和依赖项 ### 验证打包结果 1. 检查 JAR 内容: ```bash jar tf target/HomeWork_Java_Coding-bundle.jar ``` 应看到两个模块的类文件: ``` com/example/EmployeeSystem.class com/example/PrimeCount.class META-INF/MANIFEST.MF ``` 2. 运行程序: ```bash java -jar target/HomeWork_Java_Coding-bundle.jar ``` ### 注意事项 1. **主类配置**: - 将 `${main.class}` 替换为实际入口类,如:`com.company.MainApp` - 确保主类存在于某个子模块中 2. **多主类处理**: - 如果两个模块都有主类,需选择其中一个作为入口 - 或创建新的主类模块协调启动 3. **依赖冲突**: - 如果子模块有不同版本的相同依赖,Assembly 插件会自动选择最高版本 - 可通过 `<excludes>` 手动解决冲突
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值