“Rust”中的“array struct”用于固定长度数组。

huangapple go评论69阅读模式
英文:

rust “array struct” for constant length arrays

问题

For 3D-modelling based on triangles, I use a C struct like

pub struct Point {
    pub x: f32,
    pub y: f32,
    pub z: f32,
};

and tuple struct like

pub struct Triangle (
    pub Point,
    pub Point,
    pub Point,
);

Algorithms based on triangles often have to iterate all points of a triangle. I can do this with

for point in [ triangle.0, triangle.1, triangle.2 ].iter() {  }

but I imagine that the conversion from Triangle to array will always cost some runtime, and I do not think this is an elegant idiom anyway. I would prefer if I could use a kind of type that I might call "array structs" of the form

pub struct Triangle [pub Point; 3];

which would itself implement iter and thus allow

for point in triangle.iter() {  }

I know that such "array struct" syntax is not available. So what would be a best practice idiom for the situation where a named type should essentially hold an array of one basic element type where the length of the array is known and fixed at compile time?

英文:

For 3D-modelling based on triangles, I use a C struct like

pub struct Point {
    pub x: f32,
    pub y: f32,
    pub z: f32,
};

and tuple struct like

pub struct Triangle (
    pub Point,
    pub Point,
    pub Point,
);

Algorithms based on triangles often have to iterate all points of a triangle. I can do this with

for point in [ triangle.0, triangle.1, triangle.2 ].iter() {  }

but I imagine that the conversion from Triangle to array will always cost some runtime, and I do not think this is an elegant idiom anyway. I would prefer if I could use a kind of type that I might call “array structs” of the form

pub struct Triangle [pub Point; 3];

which would itself implement iter and thus allow

for point in triangle.iter() {  }

I know that such “array struct” syntax is not available. So what would be a best practice idiom for the situation where a named type should essentially hold an array of one basic element type where the length of the array is known and fixed at compile time?

答案1

得分: 1

如果你有小数组,比如少于10-12个元素,这不会引起太多问题,因为编译器会展开循环并通过直接从三角形中复制来删除数组。

请参见 godbolt

例如,这段代码:

pub fn update_triangles(triangle: &Triangle, process: fn(Point)){
    for point in [ triangle.0, triangle.1, triangle.2 ].iter() { 
        process(*point);
    }
}

会转化为以下汇编代码:

example::update_triangles:
        push    r15
        push    r14
        push    rbx
        sub     rsp, 16
        mov     rbx, rsi
        mov     r14, rdi
        mov     eax, dword ptr [rdi + 8]
        mov     dword ptr [rsp + 8], eax
        mov     rax, qword ptr [rdi]
        mov     qword ptr [rsp], rax
        mov     r15, rsp
        mov     rdi, r15
        call    rsi
        mov     eax, dword ptr [r14 + 20]
        mov     dword ptr [rsp + 8], eax
        mov     rax, qword ptr [r14 + 12]
        mov     qword ptr [rsp], rax
        mov     rdi, r15
        call    rbx
        mov     eax, dword ptr [r14 + 32]
        mov     dword ptr [rsp + 8], eax
        mov     rax, qword ptr [r14 + 24]
        mov     qword ptr [rsp], rax
        mov     rdi, r15
        call    rbx
        add     rsp, 16
        pop     rbx
        pop     r14
        pop     r15
        ret

然而,对于更大的类型和数组,情况可能不成立。

另一种选择是为你的类型实现迭代器:

impl Triangle {
    fn points(&'_ self)->impl Iterator<Item=Point> + '_' {
        (0..3).map(move|i|
            match i {
                0 => self.0,
                1 => self.1,
                2 => self.2,
                _ => unreachable!(),
            }
        )
    }
}

pub fn update_triangles(triangle: &Triangle, process: fn(Point)){
    for point in triangle.points() { 
        process(point);
    }
}

在这种情况下,它生成了没有额外内存使用的展开循环:godbolt

不过,你需要小心,因为如果编译器在优化时失败,与使用数组相比,你会得到更多的分支,这可能导致代码执行较慢。

1: https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,selection:(endColumn:18,endLineNumber:15,positionColumn:18,positionLineNumber:15,selectionStartColumn:18,selectionStartLineNumber:15,startColumn:18,startLineNumber:15),source:'#[derive(Clone,Copy)]\npub struct Point{\n pub x:f32,pub y:f32,pub z:f32\n}\n\n#[derive(Clone,Copy)]\npub struct Triangle(\n pub Point,\n pub Point,\n pub Point,\n);\n\npub fn update_triangles(triangle: &Triangle, process: fn(Point)){\n for point in [ triangle.0, triangle.1, triangle.2 ].iter() { \n process(*point);\n }\n}\n\npub fn update_triangles_inline(\n triangle: &Triangle, process: fn(Point)\n){\n process(triangle.0);\n process(triangle.1);\n process(triangle.2);\n}' ),l:'5',n:'0',o:'Rust source #1',t:'0')),k:42.18941996092467,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:r1700,deviceViewOpen:'1',filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,libs:!(),options:'-Copt-level=3',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+rustc+1.70.0+(Editor+#1)',t:'0')),k:32.883854902479285,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compilerName:'clang+16.0.0',editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output+of+rustc+1.70.0+(Compiler+#1)',t:'0')),k:24.926725136596044,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4
2: https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,selection:(endColumn:2,endLineNumber:30,positionColumn:1,positionLineNumber:13,selectionStartColumn:2,selectionStartLineNumber:30,startColumn:1,startLineNumber:13),source:'#[derive(Clone,Copy)]\npub struct Point{\n pub x:f32,pub y:f32,pub z:f32\n}\n\n#[derive(Clone,Copy)]\npub struct Triangle(\n pub Point,\n pub Point,\n pub Point,\n);\n\nimpl Triangle {\n fn points(&'_ self)->impl Iterator<Item=Point> + '_' {\n (0..3).map(move|i|\n match i {\n 0 => self.0,\n 1 => self.1,\n 2 => self.2,\n _ => unreachable!(),\n }\n )\n }\n}\n\npub fn update_triangles(triangle

英文:

If you have tiny arrays like less than 10-12 elements, it wouldn't cause a lot of problems because compiler unrolls loop and removes array as well by using direct copies from triangle.

See godbolt.

For example, this code:

pub fn update_triangles(triangle: &amp;Triangle, process: fn(Point)){
    for point in [ triangle.0, triangle.1, triangle.2 ].iter() { 
        process(*point);
    }
}

converts to this asm:

example::update_triangles:
        push    r15
        push    r14
        push    rbx
        sub     rsp, 16
        mov     rbx, rsi
        mov     r14, rdi
        mov     eax, dword ptr [rdi + 8]
        mov     dword ptr [rsp + 8], eax
        mov     rax, qword ptr [rdi]
        mov     qword ptr [rsp], rax
        mov     r15, rsp
        mov     rdi, r15
        call    rsi
        mov     eax, dword ptr [r14 + 20]
        mov     dword ptr [rsp + 8], eax
        mov     rax, qword ptr [r14 + 12]
        mov     qword ptr [rsp], rax
        mov     rdi, r15
        call    rbx
        mov     eax, dword ptr [r14 + 32]
        mov     dword ptr [rsp + 8], eax
        mov     rax, qword ptr [r14 + 24]
        mov     qword ptr [rsp], rax
        mov     rdi, r15
        call    rbx
        add     rsp, 16
        pop     rbx
        pop     r14
        pop     r15
        ret

This may be not true for larger types and arrays though.

Another option is to implement iterator for your type:

impl Triangle {
    fn points(&amp;&#39;_ self)-&gt;impl Iterator&lt;Item=Point&gt; + &#39;_ {
        (0..3).map(move|i|
            match i {
                0 =&gt; self.0,
                1 =&gt; self.1,
                2 =&gt; self.2,
                _ =&gt; unreachable!(),
            }
        )
    }
}

pub fn update_triangles(triangle: &amp;Triangle, process: fn(Point)){
    for point in triangle.points() { 
        process(point);
    }
}

In this case, it generates unrolled loop without extra memory usage too: godbolt.

You need to be careful with that though because if compiler would fail with optimization, you would get more branches than when using array which may lead to slower code execution.

答案2

得分: 0

我可以使用命名类型,就像这样:

type Triangle = [ Point; 3 ];

然后我可以循环遍历 triangle.iter(),甚至可以更简单地不使用显式的 iter

for point in triangle {  }

(在问题中的代码中也可以省略 iter()。)

英文:

Instead of a struct I have found out I can use a named type like this:

type Triangle = [ Point; 3 ];

Then I can loop over triangle.iter(), and even simpler without explicit iter:

for point in triangle {  }

(Dropping iter() would have been possible in the code in the question, too.)

huangapple
  • 本文由 发表于 2023年6月12日 22:24:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76457610.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定