当前位置: 首页 > 知识库问答 >
问题:

GLDrawArray从未比小数组的glBegin/glEnd更快?

羊舌庆
2023-03-14

对于客户端项目,我需要一个在旧硬件上工作的简单精灵闪电战。OpenGL 1.1似乎是一个简单的答案,因为我有可以重用的旧代码。

无论如何,一切都很好,但令我大吃一惊的是,事实证明,对于移动的子画面(因此在正交投影中渲染纹理四边形),glBegin/glTexCoord2/glVertex2/glEnd模式总是和glDrawArrays一样快。这在旧硬件和新硬件上都进行了测试,我有点困惑,因为我期望得到不同的结果!

不过要注意的是,这两种模式对于需求来说都是足够快的(所以这真的是奇怪的书呆子谈话,而不是严肃的工作谈话!),但是客户端的演示允许抽取精灵数达到10000或更多,然后我们注意到启用gldrawarrays选项通常是相同的速度,在一些机器上是glbegin/glend的一半。

下面是代码的一部分。注意,在这个演示中,顶点和纹理数组是相邻的全局变量。

for index:=0 to (sprite_list.count-1) do begin
 s:=sprite_list[index];
 s.update;
 glBindTexture(GL_TEXTURE_2D,s.sprite_id);
 glColor4b(127,127,127,s.ialpha);
 if immediate then begin
  glBegin(GL_QUADS);
   glTexCoord2f(0,0); glVertex2i(coords[0].x,coords[0].y);
   glTexCoord2f(0,1); glVertex2i(coords[1].x,coords[1].y);
   glTexCoord2f(1,1); glVertex2i(coords[2].x,coords[2].y);
   glTexCoord2f(1,0); glVertex2i(coords[3].x,coords[3].y);
  glEnd();
 end else 
  glDrawArrays(GL_QUADS, 0, 4);

编辑:这是delphi单元的代码。使用表单创建一个新项目,在其上添加timer1(启用,间隔=1)和timer2(禁用,间隔=1)对象,将单元代码替换为this,插入表单事件:doubleclick/keydown/resize/销毁。请注意,这是在旧版本的delphi中编译的,因此在单元的开头添加了一些opengl标头。此外,按左/右更改精灵数量,并在glDrawArray和glStart in/glEnd之间切换空间。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Timer2: TTimer;
    procedure Timer1Timer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormDblClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
 opengl;

const
 GL_BGRA_EXT = $80E1;
 GL_VERTEX_ARRAY = $8074;
 GL_TEXTURE_COORD_ARRAY = $8078;

type
 PGLvoid = Pointer;

procedure glDeleteTextures(n: GLsizei; textures: pGLuint);stdcall;external opengl32;
procedure glGenTextures(n: GLsizei; textures: pGLuint);stdcall;external opengl32;
procedure glBindTexture(target: GLenum; texture: GLuint);stdcall;external opengl32;
procedure glEnableClientState(state: GLenum);stdcall;external opengl32;
procedure glDisableClientState(state: GLenum);stdcall;external opengl32;
procedure glTexCoordPointer(size: GLint; _type: GLenum; stride: GLsizei; const _pointer: PGLvoid);stdcall;external opengl32;
procedure glVertexPointer(size: GLint; _type: GLenum; stride: GLsizei; const _pointer: PGLvoid);stdcall;external opengl32;
procedure glDrawArrays(mode: GLenum; first: GLint; count: GLsizei);stdcall;external opengl32;

type
 tgeo_point=record
  x,y:longint;
 end;

var
 gl_Texture_Coordinates:array [0..7] of single=(0,0,0,1,1,1,1,0);
 coords:array [0..3] of tgeo_point;
 immediate:boolean=false;

type
 tsprite=class
  private
   ix,iy:longint;
   ix_dir,iy_dir:longint;
   ialpha:longint;
  public
   constructor create;
   destructor Destroy;override;

   procedure update(w,h:longint);
 end;

var
 gl_dc:hdc;
 gl_pixel_format:longint;
 gl_context:longint;
 gl_sprite_id:cardinal;
 sprite:array [0..1023] of dword;
 sprite_width:longint=32;
 sprite_height:longint=32;
 sprite_list:tlist;
 times:array [0..10] of longint=(0,0,0,0,0,0,0,0,0,0,0);

procedure gl_init;
var
 p,p2:tpixelformatdescriptor;
begin
 gl_dc:=getdc(form1.handle);

 zeromemory(@p,sizeof(p));
 p.nSize:=sizeof(p);
 p.nVersion:=1;
 p.dwFlags:=PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
 p.iPixelType:=PFD_TYPE_RGBA;
 p.cColorBits:=32;
 p.iLayerType:=PFD_MAIN_PLANE;

 gl_pixel_format:=choosepixelformat(gl_dc,@p);
 if gl_pixel_format=0 then
  showmessage('error');
 if not setpixelformat(gl_dc,gl_pixel_format,@p) then
  showmessage('error');
 describepixelformat(gl_dc,gl_pixel_format,sizeof(p2),p2);
 if ((p.dwFlags and p2.dwFlags)<>p.dwFlags) or
    (p.iPixelType<>p2.iPixelType) or
    (p.cColorBits<>p2.cColorBits) or
    (p.iLayerType<>p2.iLayerType) then
  showmessage('errrrror');

 gl_context:=wglcreatecontext(gl_dc);
 if gl_context=0 then
  showmessage('error');
 if not wglmakecurrent(gl_dc,gl_context) then
  showmessage('error');

 glEnable(GL_BLEND);
 glEnable(GL_TEXTURE_2D);
 glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

 glViewport(0,0,form1.clientwidth,form1.clientheight);
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 glOrtho(0,form1.clientwidth,0,form1.clientheight,-1,1);
 glMatrixMode(GL_MODELVIEW);

 glColor4f(1,1,1,1);

 glEnableClientState(GL_VERTEX_ARRAY);
   glVertexPointer(2, GL_INT, 0, @coords);
 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 glTexCoordPointer(2,gl_float,0,@gl_Texture_Coordinates);

 glClearColor(0,0,0,1);
 glClear(GL_COLOR_BUFFER_BIT);
 SwapBuffers(gl_dc);
end;

procedure gl_un_init;
begin
 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 glDisableClientState(GL_VERTEX_ARRAY);
 wgldeletecontext(gl_context);
 releasedc(form1.handle,gl_dc);
end;

procedure gl_resize;
begin
 glViewport(0,0,form1.clientwidth,form1.clientheight);
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 glOrtho(0,form1.clientwidth,0,form1.clientheight,-1,1);
 glMatrixMode(GL_MODELVIEW);
end;

function make_color(a,r,g,b:longint):cardinal;
begin
 result:=(a and 255) shl 24 or
         (r and 255) shl 16 or
         (g and 255) shl 8 or
         (b and 255);
end;

procedure sprite_init;
var
 x,y:longint;
begin
 for x:=0 to (sprite_width-1) do
  for y:=0 to (sprite_height-1) do
   sprite[y*(sprite_width)+x]:=
    make_color((x div 2+1)*(y div 2+1)-1,$ff,$ff,$ff);

 glgentextures(1,@gl_sprite_id);
 glBindTexture(GL_TEXTURE_2D,gl_sprite_id);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_nearest);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_nearest);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_clamp);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_clamp);

 glTexImage2D(GL_TEXTURE_2D,0,4,sprite_width,sprite_height,0,GL_BGRA_EXT,
              GL_UNSIGNED_BYTE,@sprite);
end;

procedure sprite_un_init;
begin
 gldeletetextures(1,@gl_sprite_id);
end;

constructor tsprite.create;
begin
 inherited create;

 ix:=ranhtml" target="_blank">dom(form1.clientwidth);
 iy:=random(form1.clientheight);

 if random(2)=1 then
  ix_dir:=1
 else
  ix_dir:=-1;

 if random(2)=1 then
  iy_dir:=1
 else
  iy_dir:=-1;

 ialpha:=random(128);
end;

destructor tsprite.Destroy;
begin
 inherited destroy;
end;

procedure tsprite.update(w,h:longint);
begin
 if ix_dir=-1 then begin
  dec(ix);
  if ix<0 then begin
   ix:=0;
   ix_dir:=1;
  end;
 end else begin
  inc(ix);
  if ix>=w then begin
   ix:=w;
   ix_dir:=-1;
  end;
 end;

 if iy_dir=-1 then begin
  dec(iy);
  if iy<0 then begin
   iy:=0;
   iy_dir:=1;
  end;
 end else begin
  inc(iy);
  if iy>=h then begin
   iy:=h;
   iy_dir:=-1;
  end;
 end;

 coords[0].x:=ix;
 coords[0].y:=iy;
 coords[1].x:=ix;
 coords[1].y:=iy+sprite_height;
 coords[2].x:=ix+sprite_width;
 coords[2].y:=iy+sprite_height;
 coords[3].x:=ix+sprite_height;
 coords[3].y:=iy;
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
 index:longint;
begin
 for index:=0 to (sprite_list.count-1) do
  tsprite(sprite_list[index]).free;
 sprite_list.free;
 sprite_un_init;
 gl_un_init;
end;

// --nVidia video card memory
//const
// GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX=$9048;
// GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX=$9049;

procedure TForm1.FormDblClick(Sender: TObject);
var
 a,b:longint;
begin
// glGetIntegerv(GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX,@a);
// glGetIntegerv(GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX,@b);
 showmessage(
  glgetstring(GL_VENDOR)+#13#10+
 glgetstring(GL_RENDERER)+#13#10+
 glgetstring(GL_VERSION)
 +#13#10+'Memory: '+inttostr(b)+'/'+inttostr(a)
// +#13#10+glgetstring(GL_EXTENSIONS)
 );
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
 index:longint;
begin
 case key of
  vk_space:immediate:=not immediate;
  vk_escape:form1.close;
  vk_left:if sprite_list.count>0 then
            for index:=(sprite_list.count-1) downto (sprite_list.count-100) do begin
             tsprite(sprite_list[index]).free;
             sprite_list.delete(index);
            end;
  vk_right:for index:=1 to 100 do sprite_list.add(tsprite.create);
 end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
 gl_resize;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
 timer1.enabled:=false;
 timer2.enabled:=true;

 gl_init;
 sprite_init;
 sprite_list:=tlist.create;
end;

procedure TForm1.Timer2Timer(Sender: TObject);
var
 index,w,h,elapsed:longint;
 s:tsprite;
 ss:string;
begin
 glClear(GL_COLOR_BUFFER_BIT);

 w:=form1.clientwidth;
 h:=form1.clientheight;
 glBindTexture(GL_TEXTURE_2D,gl_sprite_id);
 for index:=0 to (sprite_list.count-1) do begin
  s:=sprite_list[index];
   s.update(w,h);
   glColor4b(127,127,127,s.ialpha);
   if immediate then begin
    glBegin(GL_QUADS);
     glTexCoord2f(0,0); glVertex2i(coords[0].x,coords[0].y);
     glTexCoord2f(0,1); glVertex2i(coords[1].x,coords[1].y);
     glTexCoord2f(1,1); glVertex2i(coords[2].x,coords[2].y);
     glTexCoord2f(1,0); glVertex2i(coords[3].x,coords[3].y);
    glEnd();
   end else
    glDrawArrays(GL_QUADS, 0, 4);
 end;
 glBindTexture(GL_TEXTURE_2D,0);

 SwapBuffers(gl_dc);

 for index:=10 downto 1 do
  times[index]:=times[index-1];
 times[0]:=gettickcount;
 elapsed:=times[0]-times[10];
 if elapsed=0 then elapsed:=1;

 if immediate then
  ss:='glBegin/glEnd '
 else
  ss:='glDrawArrays  ';
 form1.caption:=ss+'Sprites: '+inttostr(sprite_list.count)+' / FPS: '+inttostr(10*1000 div elapsed);
end;

end.

edit2:非常抱歉,我忘了说,在这种情况下,每个精灵的单一纹理对最终结果至关重要,即使我通过删除代码来简化代码,将渲染循环集中在glBegin/glEnd vs glDrawArrays上。抱歉遗漏误导!

共有2个答案

向俊贤
2023-03-14
glDrawArrays(GL_QUADS, 0, 4);

您必须增加批处理大小,才能真正看到顶点阵列的性能优势。

缺点是您通常必须重新构建代码,并愿意容忍一些“冗余”存储和计算。

示例:(C语言,但除了向量数学的GLM运算符重载之外,没有什么特别的)

#include <GL/glut.h>

#include <vector>
#include <iostream>
using namespace std;

#include <glm/glm.hpp>
#include <glm/gtc/random.hpp>
using namespace glm;

class Sprites
{
public:
    struct State
    {
        State() {}
        State( const vec2& pos, const vec2& vel ) : pos(pos), vel(vel) {}
        vec2 pos;
        vec2 vel;
    };

    struct Vertex
    {
        Vertex() {}
        Vertex( const vec4& color ) : color(color) {}
        vec2 pos;
        vec4 color;
    };

    size_t Size()
    {
        return states.size();
    }

    void PushBack( const State& state, const vec4& color )
    {
        states.push_back( state );
        verts.push_back( Vertex( color ) );
        verts.push_back( Vertex( color ) );
        verts.push_back( Vertex( color ) );
        verts.push_back( Vertex( color ) );
    }

    void Add( unsigned int number )
    {
        const float w = (float)glutGet( GLUT_WINDOW_WIDTH );
        const float h = (float)glutGet( GLUT_WINDOW_HEIGHT );
        for( unsigned int i = 0; i < number; ++i )
        {
            State state( glm::linearRand( vec2(-w,-h), vec2(w,h) ), glm::diskRand( 100.0f ) );
            vec4 color( glm::linearRand( vec4(1,1,1,1) * 0.1f, vec4(1,1,1,1) ) );
            PushBack( state, color );
        }
    }

    void Remove( unsigned int number )
    {
        if( states.size() >= number ) 
            states.resize( states.size() - number );
        if( verts.size() >= number * 4 )
            verts.resize( verts.size() - number * 4 );
    }

    void Step( float dt )
    {
        // run physics
        const float w = (float)glutGet( GLUT_WINDOW_WIDTH );
        const float h = (float)glutGet( GLUT_WINDOW_HEIGHT );
        const vec2 minExts = vec2(-w, -h);
        const vec2 maxExts = vec2(w, h);
        for( int i = 0; i < (int)states.size(); ++i )
        {
            State& state = states[i];

            if( state.pos.x < minExts.x || state.pos.x > maxExts.x )
                state.vel.x = -state.vel.x;
            if( state.pos.y < minExts.y || state.pos.y > maxExts.y )
                state.vel.y = -state.vel.y;

            state.pos += state.vel * dt;
        }

        // update geometry
        const vec2 spriteDims( 32, 32 );
        const vec2 offsets[4] =
        {
            vec2( -1, -1 ) * 0.5f * spriteDims,
            vec2(  1, -1 ) * 0.5f * spriteDims,
            vec2(  1,  1 ) * 0.5f * spriteDims,
            vec2( -1,  1 ) * 0.5f * spriteDims,
        };
        for( int i = 0; i < (int)states.size(); ++i )
        {
            verts[i*4 + 0].pos = states[i].pos + offsets[0];
            verts[i*4 + 1].pos = states[i].pos + offsets[1];
            verts[i*4 + 2].pos = states[i].pos + offsets[2];
            verts[i*4 + 3].pos = states[i].pos + offsets[3];
        }
    }

    void Draw( bool useVertexArrays )
    {
        if( verts.empty() ) return;

        if( useVertexArrays )
        {
            glEnableClientState( GL_VERTEX_ARRAY );
            glVertexPointer( 2, GL_FLOAT, sizeof(Vertex), &verts[0].pos );

            glEnableClientState( GL_COLOR_ARRAY );
            glColorPointer( 4, GL_FLOAT, sizeof(Vertex), &verts[0].color );

            glDrawArrays( GL_QUADS, 0, verts.size() );

            glDisableClientState( GL_VERTEX_ARRAY );
            glDisableClientState( GL_COLOR_ARRAY );
        }
        else
        {
            glBegin( GL_QUADS );
            for( size_t i = 0; i < states.size(); ++i )
            {
                glColor4fv(  &verts[i*4 + 0 ].color.r );
                glVertex2fv( &verts[i*4 + 0 ].pos.x );
                glColor4fv(  &verts[i*4 + 1 ].color.r );
                glVertex2fv( &verts[i*4 + 1 ].pos.x );
                glColor4fv(  &verts[i*4 + 2 ].color.r );
                glVertex2fv( &verts[i*4 + 2 ].pos.x );
                glColor4fv(  &verts[i*4 + 3 ].color.r );
                glVertex2fv( &verts[i*4 + 3 ].pos.x );
            }
            glEnd();
        }
    }

private:
    vector< State > states;
    vector< Vertex > verts;
};

Sprites sprites;
bool useVAs = false;
void keyboard( unsigned char key, int x, int y )
{
    switch( key )
    {
    case 'a':   sprites.Add( 10000 );       break;
    case 'z':   sprites.Remove( 10000 );    break;
    case 'v':   useVAs = !useVAs;           break;
    case 27:    exit( 1 );                  break;
    default:    break;
    }
}

void display()
{
    static int prvTime = glutGet( GLUT_ELAPSED_TIME );
    const int curTime = glutGet( GLUT_ELAPSED_TIME );
    const float dt = ( curTime - prvTime ) / 1000.0f;
    prvTime = curTime;

    cout << "Sprites: " << sprites.Size() << "; "; 
    cout << "dt: " << dt * 1000.0f << "ms ";
    cout << endl;

    sprites.Step( dt );

    glClear( GL_COLOR_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    double w = glutGet( GLUT_WINDOW_WIDTH );
    double h = glutGet( GLUT_WINDOW_HEIGHT );
    glOrtho( -w, w, -h, h, -1, 1 );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    sprites.Draw( useVAs );

    glutSwapBuffers();
}

int main( int argc, char** argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
    glutInitWindowSize( 640, 480 );
    glutCreateWindow( "GLUT" );
    glutDisplayFunc( display );
    glutIdleFunc( display );
    glutKeyboardFunc( keyboard );

    sprites.Add( 10000 );

    glutMainLoop();
    return 0;
}
景俊语
2023-03-14

我实际上是在13年前使用OpenGL的,即使在那时我也可以告诉你,如果您的应用程序结构正确,顶点数组通常会更快。

那时我们还没有顶点缓冲区对象,但是我们有交错顶点数组(我的字面意思是glInterleavedArray(...))和编译顶点数组。英伟达后来创建了一个扩展(顶点数组范围),允许顶点数据存储在虚拟内存中(该虚拟内存的地址范围旨在允许高效的DMA传输)。

AMD,当时仍然被称为ATI,也有自己的扩展,它提高了顶点数组性能(通过在服务器端存储顶点数据),称为顶点数组对象。不要将AMD的扩展与我们在现代OpenGL中所说的VAO混淆。实际上,AMD的扩展为顶点缓冲区对象奠定了基础,它只是不幸地与完全不相关的东西共享它的名字。

现在,我刚刚讨论的所有内容实际上都描绘了OpenGL中顶点html" target="_blank">数组的演变。特别是,它们表明趋势是(1)将顶点数据存储在用户定义的内存组织中(2)存储在服务器(GPU)内存中以获得最大的重用,以及(3)尽可能少的API调用。即时模式(glStart in/glEnd)违反了所有这些原则,您可以使用即时模式将命令放入显示列表并希望驱动程序处理第2点和第3点。

还要注意,因为我们谈论的硬件可以追溯到OpenGL 1.1时代,图形硬件并不总是处理顶点变换。在很长一段时间里,“GPU”的祖先只加速光栅化,顶点变换是在CPU上处理的。在我们拥有可以实现整个图形管道的GPU之前,顶点和光栅化之间的分离意味着顶点传递到管道中的效率并不重要,因为一些工作是在CPU上完成的。一次硬件测试

这就是问题的症结所在。您的软件的设置方式不会从顶点变换的硬件加速中受益。您正在CPU上进行所有转换,并在需要更改时随时向GPU发送新数据。现代应用程序使用顶点着色器和静态顶点数据来做同样的事情,同时最大限度地减少每帧需要发送到GPU的数据量。在现代软件中,大多数转换都可以通过更新一两个4x4矩阵来完成,以便在顶点着色器中使用。

最重要的是,除非你的软件是顶点受限的,否则提高顶点效率不会显著提高性能。“精灵blitting”对我来说听起来更像是一个碎片绑定的场景,特别是如果精灵是alpha混合的。

 类似资料:
  • 我希望TPP是一个数组,每个阈值都有TPP值。打印应该是这样的:TPP是:n1,n2。。。

  • 基本上,我要问的是给定一个正方形2D阵列和一个有效的补丁大小(2D子阵列的大小),我将如何做到这一点。最终,我不需要以任何方式存储子阵列,我只需要找到每个子阵列的中值并将它们存储在一个一维阵列中。中值和存储到新阵列对我来说很简单,我只是不知道如何处理原始2D阵列并正确拆分它。我已经尝试了几次,但一直出现越界错误。我有一个4x4: 我需要像这样拆分它 < code>[1,2] [3,4] [2,3]

  • 本文向大家介绍JavaScript中数组slice和splice的对比小结,包括了JavaScript中数组slice和splice的对比小结的使用技巧和注意事项,需要的朋友参考一下 前言 今天重温了一下Javascript,看到了数组的方法,其中有两个比较相似的方法——splice和splice,看着很像,就是多了一个p,但是用法却相当不一样。 在使用中,可以通过选择一个具有强语义表达性的 AP

  • 我一直认为numpy数组比list更紧凑,占用的内存更少,但是,对于三维float64 np数组, 输出是,, 列表占用的内存要小得多。使用?如果是,我能做些什么来提高np数组内存使用率吗? ###################### 使用pympler@J_H(pympler似乎不能处理列表中的数组,比如list(一个3-D数组) 谢谢大家!!

  • 问题内容: 我试图弄清楚为什么没有触发我。这是相关控制器的片段: 在我看来,显然是正确更新的,因为我的长度范围是这样的: 我想念什么? 问题答案: 尝试或。 默认情况下,$ watch 不检查对象是否相等,而仅作为参考。因此,将总是简单地返回相同的数组引用,并且不会改变。 更新: Angular v1.1.4添加了一个$ watchCollection() 方法来处理这种情况: Shallow监视

  • 我一直在研究一个深度学习库,自己写作。在矩阵运算中,获得最佳性能对我来说是一个关键。我一直在研究编程语言及其对数字运算的性能。过了一段时间,我发现C#SIMD具有与C++SIMD非常相似的性能。所以,我决定用C#编写这个库。 首先,我测试了C#SIMD(我测试了很多东西,但是这里不写了)。我注意到,当使用较小的数组时,它的工作效果要好得多。当使用较大的数组时,效率不高。我觉得很可笑。通常情况下,当