【C语言】cache和程序访问的局部性对程序性能的影响

news2024/11/27 5:25:08

文章目录

  • 1.源程序比较其性能影响
  • 2.内存分配
    • (1)静态存储区(static):
    • (2)栈区(stack):
    • (3)堆区(heap):
    • (4)文字常量区:
    • (5)程序代码区:
    • 举例:
    • 注意字符串“abc”与“123”的区别:
  • 3.程序执行时间
    • (1)`time( )`
    • (2)`clock( )`
    • (3)`QueryPerformanceCounter( )`
  • (4)`GetTickCount( )`
  • (5)`RDTSC指令`
    • 上述计时方法的比较:
  • 源程序和执行结果分析:
    • 源程序和执行结果
      • 1. 给出源程序(文本文件)和执行结果。
      • 2.对实验结果进行分析,说明局部数据块大小、数组访问顺序等和执行时间之间的关系。
      • (3)分析说明数组a分配在静态存储区、堆区和栈区,对for循环段的执行效率有没有影响。
  • 总结

通过实际程序的执行结果,了解程序访问的局部性对带有cache的计算机系统性能的影响。

1.源程序比较其性能影响

在以下程序中,修改或添加必要的语句(如添加计时函数等),以计算和打印主体程序段(即for循环段)的执行时间。分别以M=10、N=100000;M=1000、N=1000;M=100000、N=10;执行程序A和程序B,以比较两种for循环段执行时间的长短。
下列程序中给出的数组a是局部临时变量,分配在栈中,也可改用静态全局变量,或在堆中动态申请空间。

  • 程序段A:
void assign_array_rows( )
{
    int i, j;
    short a[M][N];
    for(i= 0; i<M; i++)
        for(j= 0; j<N; j++)
            a[i][j]=0;
}
  • 程序段B:
void assign_array_cols( )
{
    int i, j;
    short a[M][N];
    for(j= 0; j<N; j++)
        for(i=0; i<M; i++)
            a[i][j]=0;
}

2.内存分配

可编程内存基本上分为:静态存储区、堆区和栈区等。它们的功能不同,使用方式也不同。

(1)静态存储区(static):

在程序编译的时候就已经分配好,这块内存在程序的运行期间都存在,程序结束后由系统释放。它主要存放静态数据、全局数据和常量。

(2)栈区(stack):

由编译器自动分配、释放,无需手工控制。在执行函数时,用来存放函数内局部变量,函数执行结束时这些存储单元自动被释放。
栈区是向低地址扩展的数据结构,是一块连续的内存的区域。栈区的操作方式类似于数据结构中的栈,是一个先进后出的队列,进出一一对应,不会产生碎片。
栈内存分配运算内置于处理器的指令集中,效率很高。栈区栈顶的地址和栈的最大容量是系统预先规定好的,在 Windows下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),因此,栈区分配的内存容量较小。如果申请的空间小于栈的剩余空间,系统会为程序分配内存,如果申请的空间超过栈的剩余空间,系统会报异常,提示栈溢出overflow

(3)堆区(heap):

由程序员分配、释放,若程序员不释放,程序结束时可能由OS回收。亦称动态内存分配,程序在运行的时候用mallocnew申请任意大小的内存,程序员自己负责在适当的时候用freedelete释放内存,容易产生memory leak(内存泄漏)
堆区操作由C/C++函数库提供,机制很复杂,所以堆的效率比栈低很多;另外,频繁的new/delete造成大量碎片,也会使程序效率降低。
堆区分配方式类似于链表。系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,删除空闲结点链表中的该结点,并将该结点空间分配给程序。大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存空间;另外,系统会将多余的部分重新放入空闲链表中。堆区大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G),所以堆的空间比较灵活,比较大。
堆是向高地址扩展的数据结构,是不连续的内存区域(因为系统是用链表来存储堆区的空闲内存地址的,自然堆区是不连续的内存区域,而链表的遍历方向是由低地址向高地址)。

(4)文字常量区:

存放常量字符串,程序结束后由系统释放。

(5)程序代码区:

存放程序的二进制代码。

举例:

#include <stdio.h>
#include <stdlib.h>
int a=0;//全局变量,存放在静态存储区
int main()
{ 
    int b;//局部变量,存放在栈区
    static int c;//静态局部变量,存放在静态存储区
    char s[]="abc";//数组的元素属于局部变量,存放在栈区
    char *p="123";//字符串常量"123"存放在静态存储区,指针变量p存放在栈区
    char *p1=(char *)malloc(10);
     //指针变量p1存放在栈区,申请来得10个字节的区域在堆区
}

注意字符串“abc”与“123”的区别:

  1. 字符串“abc”、“123”存放在不同的区域,因为“abc”存在于数组中,即存储于栈区,而“123”为字符串常量,存储在静态存储区。
  2. “abc”是在程序运行时赋值的,而“123”是在编译时就确定的。
  3. 假设有如下语句
    s[0]='0';
    p[0]='0';
    语句①没有问题,“abc”存放于数组中,此数据存储于栈区,对它修改是没有任何问题的。
    但语句②有问题,“123”为字符串常量,存储在静态存储区,虽然通过p[0]可以访问到静态存储区中的第一个数据,即字符‘l’所在的存储的单元。但是因为“123”为字符串常量,不可以改变,所以语句“p[0]='0';”在程序运行时,会报告内存错误。

3.程序执行时间

通常先调用计时函数记下当前时间tstart,然后处理一段程序,再调用计时函数记下处理后的时间tend,就可以得到程序的执行时间(t = tend - tstart)
常用计时方法有:time、clcok、QueryPerformanceCounter、GetTickCount、RDTSC

(1)time( )

获取当前的系统时间,返回的结果是一个time_t类型,其实就是一个大整数,其值表示从CUT(Coordinated Universal Time)时间1970年1月1日00:00:00(称为UNIX系统的Epoch时间)到当前时刻的秒数。

#include <stdio.h>
void fun( )
{
    //dosomething
    long i;
    for(i=0;i<1000000000;i++);

}
int main()
{ 
    time_t start,stop;//定义时间类型变量
    start = time(NULL);
    fun(); 
    stop = time(NULL);
    printf("Use Time:%ld\n",(stop-start));
}
  • 程序运行结果:Use Time:1

(2)clock( )

函数返回从“开启这个程序进程”到“程序中调用clock( )函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock)。常量CLOCKS_PER_SEC表示一秒钟会有多少个时钟计时单元。

#include <stdio.h>
#include <time.h>//包含头文件
void fun( )
{
    //dosomething
    long i;
    for(i=0;i<1000000000;i++);

}
int main()
{ 
    double dur;
    clock_t start,end;//定义时钟类型变量
    start = clock();
    fun(); 
    end = clock();
    dur = (double)(end - start);
    printf("Use Time:%f\n",(dur/CLOCKS_PER_SEC));
    //时钟个数除以时钟频率即为所花时间
}
  • 程序运行结果:Use Time:1.980000

(3)QueryPerformanceCounter( )

函数返回高精确度性能计数器的值,它可以以微妙为单位计时。但是QueryPerformanceCounter()确切的精确计时的最小单位是与系统有关的,所以,必须要查询系统以得到QueryPerformanceCounter()返回的嘀哒声的频率。QueryPerformanceFrequency()提供了这个频率值,返回每秒嘀哒声的个数。

#include <stdio.h>  
#include <windows.h>//包含头文件 
void fun( )
{
    //dosomething
    long i;
    for(i=0;i<1000000000;i++);    
}
int main()  
{    
    LARGE_INTEGER t1,t2,tc;
    QueryPerformanceFrequency(&tc);
    QueryPerformanceCounter(&t1);
    fun();
    QueryPerformanceCounter(&t2);
    printf("Use Time:%f\n",(t2.QuadPart - t1.QuadPart)*1.0/tc.QuadPart);
    //前后滴答数之差除以1秒钟的滴答数,即得时间秒数
}  
  • 程序运行结果:Use Time:1.821330

(4)GetTickCount( )

函数返回从操作系统启动到现在所经过的毫秒数,它的返回值是DWORD

#include <stdio.h>  
#include <windows.h>//包含头文件 
void fun( )
{
    //dosomething
    long i;
    for(i=0;i<1000000000;i++);    
}
int main()  
{    
    DWORD t1,t2;
    t1 = GetTickCount();
    fun();//dosomething
    t2 = GetTickCount();
    printf("Use Time:%f\n",(t2-t1)*1.0/1000);//将毫秒值转成秒值
}  
  • 程序运行结果:Use Time:1.937000

(5)RDTSC指令

rdtsc 指令是 x86 架构下的一条汇编指令,全称 “Read Time-Stamp Counter”,用于读取时间戳计数器(TSC),CPU 的每个时钟周期会对其(TSC)进行加一操作。通过rdtsc指令读取这个计数器,可以测量程序某段代码的执行时间。
时间戳计数器(TSC)是一个64位的寄存器,低32位存储在EAX寄存器(在 64 位架构下为RAX寄存器),高32位存储在EDX寄存器(在 64 位架构下为RDX寄存器)。通常使用内联汇编“__asm__ volatile("rdtsc" : "=a"(low), "=d"(high));”将高32位和低32位组合成一个64位无符号整数,作为rdtsc( )函数的返回值。

#include <stdio.h>  
#include <stdint.h>//包含头文件
#define FREQUENCY (3.2*1e9)
//3.2GHz,需要事先查看自己电脑的CPU工作频率,定义成符号常量
void fun( )
{
    //dosomething
    long i;
    for(i=0;i<1000000000;i++);    
}
static inline uint64_t rdtsc()
{
    uint32_t low, high;
    __asm__ volatile("rdtsc" : "=a"(low), "=d"(high));
    return (uint64_t)high << 32 | low;
}
int main()  
{    
    uint64_t t1,t2;
    t1 = rdtsc();
    fun();
    t2 = rdtsc();
    printf("Use Time:%f\n",(t2 - t1)*1.0/FREQUENCY);//FREQUENCY为CPU的频率
}
  • 程序运行结果:Use Time:1.788094

上述计时方法的比较:

源程序和执行结果分析:

源程序和执行结果

1. 给出源程序(文本文件)和执行结果。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define M 10  
#define N 100000  

// 静态存储区数组
static short staticArray[M][N];

void assign_array_rows_static() {
    int i, j;
    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            staticArray[i][j] = 0;
}

void assign_array_cols_static() {
    int i, j;
    for (j = 0; j < N; j++)
        for (i = 0; i < M; i++)
            staticArray[i][j] = 0;
}

void assign_array_rows_stack() {
    int i, j;
    short stackArray[M][N];  // 栈区数组
    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            stackArray[i][j] = 0;
}

void assign_array_cols_stack() {
    int i, j;
    short stackArray[M][N];  // 栈区数组
    for (j = 0; j < N; j++)
        for (i = 0; i < M; i++)
            stackArray[i][j] = 0;
}

void assign_array_rows_heap() {
    int i, j;
    short(*heapArray)[N] = (short(*)[N])malloc(M * sizeof(short[N]));  // 堆区数组
    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            heapArray[i][j] = 0;
    free(heapArray);
}

void assign_array_cols_heap() {
    int i, j;
    short(*heapArray)[N] = (short(*)[N])malloc(M * sizeof(short[N]));  // 堆区数组
    for (j = 0; j < N; j++)
        for (i = 0; i < M; i++)
            heapArray[i][j] = 0;
    free(heapArray);
}

double getElapsedTime(LARGE_INTEGER start, LARGE_INTEGER end, LARGE_INTEGER frequency) {
    return ((double)(end.QuadPart - start.QuadPart) / frequency.QuadPart) * 1000;
}

void measure_time(void (*f)(), const char* desc) {
    LARGE_INTEGER start, end, frequency;
    double elapsed;

    QueryPerformanceFrequency(&frequency);
    QueryPerformanceCounter(&start);

    f();

    QueryPerformanceCounter(&end);

    elapsed = getElapsedTime(start, end, frequency);

    printf("%s: %f milliseconds\n", desc, elapsed);
}

int main() {
    printf("M=%d, N=%d\n", M, N);
    measure_time(assign_array_rows_static, "Static - Rows");
    measure_time(assign_array_cols_static, "Static - Cols");
    measure_time(assign_array_rows_stack, "Stack - Rows");
    measure_time(assign_array_cols_stack, "Stack - Cols");
    measure_time(assign_array_rows_heap, "Heap - Rows");
    measure_time(assign_array_cols_heap, "Heap - Cols");
    return 0;
}

在这里插入图片描述

在这里插入图片描述

2.对实验结果进行分析,说明局部数据块大小、数组访问顺序等和执行时间之间的关系。

根据上面的数据,我们可以得出以下结论:

  1. 理论上:静态存储区按行访问比按列访问速度更快,这是因为静态存储区的元素是在内存中连续存储的,按行访问时可以利用CPU缓存的局部性原理,将靠近的元素一起读入缓存,提高访问速度。
    但是,实际上:按列赋值的时间更短,这其中可能的原因是:
    (1)编译器优化:不同的编译器可能对代码进行不同的优化。在这样的情况下,按列赋值的代码可能被编译器优化得更好,生成更高效的机器代码。
    (2)缓存局部性:按列赋值可能更好地利用了缓存局部性。由于内层循环按列访问数组元素,可能更接近数组在内存中的布局方式,从而更容易利用CPU的缓存机制,减少内存访问延迟。
    (3)并行计算:现代CPU通常具有多个处理单元,可以执行并行计算。按列赋值可能更容易实现并行计算,因为内层循环的迭代是独立的,可以同时进行多个迭代。

  2. 栈区比静态区和堆区快的原因主要有两个方面:
    (1)内存分配方式:栈区采用的是先进后出的方式进行内存的分配和释放,而静态区和堆区则是通过动态内存管理进行分配和释放。由于栈区的分配和释放操作相对简单,在编译时就确定了变量的生命周期和作用域,因此执行效率更高。
    (2)访问方式:栈区的数据访问是通过指针进行的,而静态区和堆区通过指针或引用进行访问。指针直接指向栈区的数据,访问速度更快;而静态区和堆区的数据需要通过指针或引用来间接访问,多了一层间接寻址的过程,相对较慢。
    需要注意的是,栈区虽然速度较快,但是其空间有限,内存分配和释放都是自动完成的,不适合存储大量的数据或者需要动态调整大小的数据结构。而静态区和堆区可以用于存储较大的数据或者需要动态管理的数据结构,但是其分配和释放操作相对复杂,效率相对较低。因此,在选择数据存储区域时,需要根据具体的需求综合考虑。

  3. 理论上:在堆区中,按行访问速度比按列访问速度稍快,这可能是因为堆区内存的分配方式导致元素不是完全连续存储的,按行访问可以利用CPU缓存的预取原理,将下一个元素提前读入缓存,避免了cache miss的情况。
    但是,实际上:按行与按列访问的速度差不多,这可能是因为编译器的优化。

  4. 在结合cache局部数据块大小、数组访问顺序等方面考虑,我们可以通过一些技巧来优化数组的访问效率,例如按照列访问时,可以将数组转置,使得元素在内存中更加连续,从而利用CPU缓存的局部性原理;另外,还可以利用循环展开、向量化等技术,使得CPU可以一次处理多个元素,提高访问速度。

(3)分析说明数组a分配在静态存储区、堆区和栈区,对for循环段的执行效率有没有影响。

根据上面的实验结果,我们可以看出:

  1. 静态存储区按列访问的时间最短,按行访问的时间稍长。说明数组在静态存储区按列存储时,访问效率较高。
  2. 栈区访问的时间最短,几乎可以忽略不计。说明数组在栈区分配时,访问效率最高。
  3. 堆区按行和按列访问的时间相差不大,但都比栈区慢,并且比静态存储区略慢一些。说明数组在堆区分配时,访问效率较低。

因此,从执行效率的角度考虑,数组在栈区分配时最优,其次是在静态存储区按列存储,最劣的是在堆区分配。但需要注意的是,数组的分配位置还应该考虑内存空间的大小和使用需求等因素,而不仅仅只考虑执行效率。

总结

通过对比不同存储区域(静态存储区、堆区和栈区)中数组的访问效率,分析了局部数据块大小、数组访问顺序等与执行时间之间的关系。实验结果表明,静态存储区按列访问最快,栈区访问速度最快,堆区访问相对较慢;同时,不同编译器、缓存机制等因素也会影响访问效率。因此,在选择存储区域时需要综合考虑执行效率、内存空间大小和使用需求等因素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1320641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何做搜索?如何做搜索优化?如何在搜索领域快速成长?

三年多的搜索研发经历&#xff0c;万亿级集群管理经历&#xff0c;集群优化搜索优化经历。将生产环境的集群&#xff0c;检索性能提升了数十倍。也遇到过大大小小的生产事故。在工作中有幸能够得到前谷歌中国首席架构陈老师的指导。在搜索方面&#xff0c;自己也积累了蛮多的经…

大模型专业术语语料如何采集!XPath高级技巧助力狩猎

最近采集大模型的某领域的专业术语语料&#xff0c;却发现一些网站的专业术语和名称解释深藏在一个 <div> 节点下的多个同级 <p> 节点中。这样的结构让爬虫在使用 .//text() 后获取文本时变得十分头疼&#xff0c;难以准确区分是术语还是解释。&#x1f613;。 不…

Backtrader 文档学习-Platform Concepts

Backtrader 文档学习-Platform Concepts 1.开始之前 导入backtrader &#xff0c;以及backtrader 的指示器、数据反馈的模块 。 import backtrader as bt import backtrader.indicators as btind import backtrader.feeds as btfeeds看看btind模块下有什么方法和属性&#x…

redis之五种基本数据类型

redis存储任何类型的数据都是以key-value形式保存&#xff0c;并且所有的key都是字符串&#xff0c;所以讨论基础数据结构都是基于value的数据类型 常见的5种数据类型是&#xff1a;String、List、Set、Zset、Hash 一) 字符串(String) String是redis最基本的类型&#xff0c;v…

Let‘s EasyV|年度作品征集:让数字孪生 看见设计的力量

转眼2023 已然迎来岁末&#xff0c;在这一年以来&#xff0c;关于「数字孪生」、关于「可视化」在遇到各类挑战的同时也在持续进化。在 2023 年&#xff0c;有越来越多人了解并投身于数字孪生可视化相关行业中来&#xff0c;持续制作打磨出更多更精彩的设计作品&#xff0c;并真…

ELF文件结构

ELF文件结构 前文结尾说到编译器编译源代码后生成的文件叫做目标文件&#xff0c;而目标文件经过编译器链接之后得到的就是可执行文件。那么目标文件到底是什么&#xff1f;它和可执行文件又有什么区别&#xff1f;链接到底又做了什么呢&#xff1f;接下来&#xff0c;我们将探…

产品调研——AI平台

本文主要记录了对腾讯云-TIONE平台、华为云-ModelArt等主流AI平台的产品调研。 交互式建模 简单点说就是提供了带训练资源的云IDE&#xff0c;使用形态包括Notebook、VsCode等。 腾讯云-TI平台 TI平台将tensorflow、pytorch、spark环境等均集成到一个Notebook容器中&#xf…

探索“超级服务器” TON:SDK 应用与开发入门

TON 是一个由多个组件构成的去中心化和开放的互联网平台&#xff0c;聚焦于实现广泛的跨链互操作性&#xff0c;同时在高可扩展性的安全框架中运作。TON 区块链被设计为分布式超级计算机或“超级服务器&#xff08;superserver&#xff09;”&#xff0c;旨在提供各种产品和服务…

分布式事务--TC服务的高可用和异地容灾

1.模拟异地容灾的TC集群 计划启动两台seata的tc服务节点&#xff1a; 节点名称ip地址端口号集群名称seata127.0.0.18091SHseata2127.0.0.18092HZ 之前我们已经启动了一台seata服务&#xff0c;端口是8091&#xff0c;集群名为SH。 现在&#xff0c;将seata目录复制一份&…

屏幕超时休眠-Android13

屏幕超时休眠-Android13 1、设置界面1.2 属性值1.2.1 默认值1.2.2 最小值限制 1.3 属性值疑问 Settings.System.SCREEN_OFF_TIMEOUT 2、超时灭屏2.1 锁定屏幕的超时2.2 屏幕灭屏的超时 3、永不休眠* 关键日志 1、设置界面 packages/apps/Settings/src/com/android/settings/dis…

(已解决)如何使用matplotlib绘制小提琴图

网上很多人使用seaborn绘制小提琴图&#xff0c;本人暂时不想学新的东西&#xff0c;就是懒。本文介绍如何使用matplotlib绘制小提琴图&#xff0c;很多其他博客只是使用最简单的语法&#xff0c;默认小提琴颜色会是蓝色&#xff0c;根本改不了。本文使用了一点高级的用法&…

深度学习环境配置超详细教程【Anaconda+Pycharm+PyTorch(GPU版)+CUDA+cuDNN】

在宇宙的浩瀚中&#xff0c;我们是微不足道的&#xff0c;但我们的思维却可以触及无尽的边界。 目录 关于Anaconda&#xff1a; 关于Pycharm&#xff1a; 关于Pytorch&#xff1a; 关于CUDA&#xff1a; 关于Cudnn&#xff1a; 一、&#x1f30e;前言&#xff1a; 二、&…

如何用 Cargo 管理 Rust 工程系列 戊

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/-OiWtUCUc3FmKIGMBEYfHQ 单元和集成测试 Rust 为单元测试提供了非常好的原生支持。 创建库工程时&#xff0c;cargo 生成的源码文件 lib.rs 自带…

32、应急响应——linux

文章目录 一、linux进程排查二、linux文件排查三、linux用户排查四、linux持久化排查4.1 历史命令4.2 定时任务排查4.3 开机启动项排查 五、linux日志分析六、工具应用 一、linux进程排查 查看资源占用&#xff1a;top查看所有进程&#xff1a;ps -ef根据进程PID查看进程详细信…

不做数据采集,不碰行业应用,专注数字孪生PaaS平台,飞渡科技三轮融资成功秘诀

12月15日&#xff0c;飞渡科技在北京举行2023年度投资人媒体见面会&#xff0c;全面分享其产品技术理念与融资之路。北京大兴经开区党委书记、管委会主任常学智、大兴经开区副总经理梁萌、北京和聚百川投资管理有限公司&#xff08;以下简称“和聚百川”&#xff09;投资总监严…

头部游戏厂商鸿蒙合作,开发岗又‘缺人‘

12月18日&#xff0c;米哈游宣布将基于HarmonyOS NEXT启动鸿蒙原生应用开发&#xff0c;成为又一家启动鸿蒙原生应用开发的头部游戏厂商。 作为一家创立于2011年的科技型文创企业&#xff0c;上海米哈游网络科技股份有限公司推出了众多高品质人气产品&#xff0c;其中包括《崩坏…

Meta与Ray-Ban合作推出了一款全新智能眼镜外观时尚,而且搭载了能够“看到“你所看到的一切的人工智能技术

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

adb详细教程(五)-复制文件、截屏、录屏

adb对于安卓移动端来说&#xff0c;是个非常重要的调试工具。在进行安卓端的开发或测试过程中&#xff0c;有时需要了截屏或录屏&#xff0c;在设备上操作完成后再将文件导入电脑非常繁琐。​如果使用adb指令在进行截屏或录屏则会便捷许多。此篇文章介绍了如何使用adb指令进行文…

LLMs 玩狼人杀:清华大学验证大模型参与复杂交流博弈游戏的能力

作者&#xff1a;彬彬 编辑&#xff1a;李宝珠&#xff0c;三羊 清华大学研究团队提出了一种用于交流游戏的框架&#xff0c;展示了大语言模型从经验中学习的能力&#xff0c;还发现大语言模型具有非预编程的策略行为&#xff0c;如信任、对抗、伪装和领导力。 近年来&#x…

React系列:配置@别名路径并配置联想

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师、爬虫、ACM算法 &#x1f492; 公众号&#xff1a;知识浅谈 &#x1f525;网站…