結局C++とRustってどっちが良いの? 5traits

■ このスレッドは過去ログ倉庫に格納されています
2023/06/30(金) 21:56:35.52ID:PDIJ4aZy
「C++の色々配慮してめんどくさい感じは好きだけど、実務になったらメモリ安全性とか考えて今後Rustに変わっていくんかな」
「うだうだ言ってないで仕事で必要なのをやればいいんだよ、趣味なら好きなのやればいい」

っていう雑談スレ。

結局C++とRustってどっちが良いの? 4traits
https://mevius.5ch.net/test/read.cgi/tech/1686046386/

関連スレ(マ板): Google&MS「バグの70%はC/C++。Rustにする」
https://medaka.5ch.net/test/read.cgi/prog/1619943288/
2023/07/03(月) 04:48:48.70ID:+YxiNHjZ
>>100
https://cpprefjp.github.io/lang/cpp17/guaranteed_copy_elision.html
2023/07/03(月) 04:56:04.09ID:+YxiNHjZ
prvalueとかxvalueとかの値のカテゴリについてはこちらが詳しい
https://cpp.rainy.me/037-rvalue-reference.html#%E6%A6%82%E8%A6%81
2023/07/03(月) 05:46:02.09ID:6xMIl2cM
>>102
thx
でもコレやっぱりこの機能をスタック領域でやってるのかどうかの明言はないですね
上の方で誰かが言ってたんですけどC++のスマポ絡みは全部ヒープでやってるって話があったのでどうなんだろと
この問題はとても難しくて私勉強してた頃は解決できてなかったんですよ
例えば

object theFunc(){
object a,b;
.......
if comp( a, b) {
return( a );
} else {
return( b );
}
}

のようなコードの場合コンパイラは実際に変数aかbかのどちらが返り値として返されるのか決定する事はできません
もうこのような場合にはほとんど必然的にコピーするしかなくなります
回避する唯一の方法はobject a,bの両方を呼び出し側のスタックに確保して使わなかった方のobjectは呼び出し側スタックの穴として我慢します
もちろん一時的にスタックに穴が開きますがその呼び出し側の処理が終われば全部御破算にされるので目を瞑る作戦です
なんか聞こえは悪いですがスタック領域中に使われる変数でほんの一瞬しか使われない変数なんか山のように出るものなのでそれを一々気にしてたらキリがありません、問題視しないといけないのは永続的に残るヒープ領域のフラグメンテーションです
じゃあいつでも何でもかんでも呼び出し側のスタックに返り値を受け取る領域確保すればいいのかといえばそれもウソでほんの数バイトの情報しかないならいる分だけコピーしていらないデータは綺麗さっぱり消してしまった方がいい時もあって、どちらが良いかコンパイラは判断する方法がCでは中々解決できないみたいな事習った記憶があります
Rustならできるのかはまだ勉強中なのでよく分からないですけど
2023/07/03(月) 06:42:22.66ID:YLpV4/YA
スタックの穴ってなんのことぞや

関数等の行き返りで、intptr_t以上のものを受け渡ししようっていうのが、そもそもの無理筋感があるんだよな
rvalueを渡すなんていうのは、渡しているのやら、いないのやら

C++時代は、自信のないことはしない、だったんだ
2023/07/03(月) 06:49:45.31ID:YLpV4/YA
>>102
どうせならこっち https://github.com/EzoeRyou/cpp-intro/blob/master/037-rvalue-reference.md
2023/07/03(月) 08:24:46.39ID:Pxa6JMwc
最適化しすぎるが故に、未定義を踏むとこういう事も起きてしまうという
https://cpplover.blogspot.com/2014/06/old-new-thing.html
107デフォルトの名無しさん
垢版 |
2023/07/03(月) 08:42:15.29ID:bFZG83Bf
何かRustだとreturn時にコピーが行われてるような気がする。
2023/07/03(月) 08:58:28.64ID:gVPXZxAL
std::move() もそうだが、最低限のコピーはしないといかんぞどうせ
生成コードみてみろよ C++ならそうするので、取るべき行動は同じ
109デフォルトの名無しさん
垢版 |
2023/07/03(月) 09:05:18.13ID:XqdEtb//
要はAddAssignとAddだと計算結果は同じでもAddAssignの方が圧倒的に速いという現象が起こったりすることがある。これはAddだとコピーをリターンするのに対してAddAssignだとselfの値は同じメモリ上で書きかえられてreturnによるコピーがないから。
2023/07/03(月) 09:32:31.64ID:XuaWdgM7
確かに WideCharToMultiByte/MultiByteToWideChar は毎回

容量確認で1回目呼び出し

確保して

容量指定して2回目呼び出し

面倒だと思ってた
2023/07/03(月) 09:54:04.75ID:gVPXZxAL
毎回書くからだろ、ライブラリ(コードスニペット)にしちゃえw
2023/07/03(月) 10:05:14.03ID:kK4jJ7eL
コンパイラが吐き出すコードなんて意図通りにならない
疑い深いなら細かくチェックしながらアセンブラで書くしかない
ループ内の重要な変数はレジスタを使っているか
無駄なコピーが無いか
ループ部分に無駄な関数呼びだしが無いか
実際のメモリに読み書きしやすい順序でデータが並んでいるか
最初に必要なメモリを一括でOSから取得しているか
最適な命令を使っているか
キャッシュ汚染が無いか
2023/07/03(月) 10:10:50.88ID:gVPXZxAL
アセンブラで書いてすら、意図通りにならないくらいに思っとけってばっちゃんが言ってた
(適当に分割して、複数パイプラインに流し込まれたりするから)
2023/07/03(月) 11:59:58.23ID:fwYpLKgv
>>96
RVOだよなぁ
2023/07/03(月) 16:06:04.98ID:+YxiNHjZ
>>103
その場合はムーブコンストラクタ実装しとけば良い
参照でない値のリターンは自動で右辺値参照となる
2023/07/03(月) 16:10:20.37ID:+YxiNHjZ
このようにC++は色々複雑すぎてもはや普通の人が理解できるものではなくなったので
素直にrust使おうよという振りなんだけどね
2023/07/03(月) 16:16:32.89ID:5E3kHz23
Rustだとどうなるの? ソースで例示プリーズ
2023/07/03(月) 16:53:27.10ID:uVoNxoEf
たとえば64bit二つ分を返すと

#[derive(Default)]
struct Foo { a: u64, b: u64, }
impl Foo {
fn new(a: u64) -> Self {
let mut foo = Foo::default();
foo.a = a;
println!("foo: 0x{:p}", &foo);
foo
}
}

fn main() {
let fff = Foo::new(123);
println!("fff: 0x{:p}", &fff);
}

レジスタ返しができる範囲なので両者のアドレスは異なる
foo: 0x0x7ffc3e73a6d8
fff: 0x0x7ffc3e73a740

一つ増やして64bit三つ分を返すと
struct Foo { a: u64, b: u64, c: u64, }

レジスタ返しではなくなり
いわゆるRVO (Return Value Optimization)
呼び出し元main()のスタックフレームに直接書き込むため両者のアドレスは同じになる
foo: 0x0x7ffc03863d78
fff: 0x0x7ffc03863d78
2023/07/03(月) 17:12:42.36ID:uVoNxoEf
前者のアドレスが異なる場合というのは
レジスタ上だけで済むところを敢えてアドレス表示させているため
実際にはスタックを使わずレジスタのみの利用に成りうることを意味してることに注意
2023/07/03(月) 18:59:43.08ID:MgkFcxqO
なるほどrustは呼び出し元スタックに直接値返せるのね
コレはC++でできるんですか?
2023/07/03(月) 20:50:38.98ID:5E3kHz23
$ cat test.cpp
#include <iostream>
#include <cstdint>
using namespace std;
struct Foo {
uint64_t a;
uint64_t b;
static Foo new_ (uint64_t a) {
Foo foo; foo.a = a;
cout << "foo: " << &foo << '\n';
return foo;
}
};
int main () {
auto fff {Foo::new_ (123)};
cout << "foo: " << &fff << '\n';
return 0;
}
$ g++ -O0 -std=c++20 -o test test.cpp
$ ./test
foo: 0x7fff0bbf3540
foo: 0x7fff0bbf3560
$ g++ -O3 -std=c++20 -o test test.cpp
$ ./test
foo: 0x7ffed05f53a0
foo: 0x7ffed05f53a0
2023/07/03(月) 21:12:04.60ID:MgkFcxqO
>>121
それは呼び出し側のスタックですか?
2023/07/03(月) 21:12:23.79ID:5E3kHz23
NRVOなのでいつも効くとは限らない?
2023/07/03(月) 21:15:14.72ID:5E3kHz23
>>122
0x7fff0bbf3560と0x7ffed05f53a0は呼び出し側のスタックなのでは?
2023/07/03(月) 22:09:08.78ID:+q7z5VN1
たったそれだけのことを
ヒープから確保してガベージコレクションで解放する重い遅いプログラミング言語がほとんどの中
C++とRustが優秀すぎるハイレベルな戦い
2023/07/03(月) 23:25:23.71ID:MgkFcxqO
>>724
そうなんですか?
厳密にいえば>>118のコードにしても関数側の変数のアドレスと返されたアドレスが同じである事の確認に過ぎず、つまり値を返す時にコピーがされてない事の確認にすぎません
それがヒープなのか、呼び出し側のスタックなのかは分からない
ただRustの場合は>>118の場合呼び出し側のスタック領域が利用される、その技術をRVOと呼ぶとアナウンスされているのに対してC++の方では今の所そのような情報ないので分からないのでは?
単にヒープに確保してるだけかもしれない
2023/07/03(月) 23:28:41.59ID:MgkFcxqO
あ、いや、今ググったらC++でもRVO実現してるみたいですね
失礼しました
しかし本当にそのアドレスの数値が一致していることだけでRVOが効いてる事の証明にはならないとは思います
2023/07/03(月) 23:31:27.59ID:MgkFcxqO
あ、よくよく読んだら「RVOとは戻り値返す時にコピーしない技術のこと」であって「呼び出し側のスタックを利用するかどうか」は別問題のようですね
まぁだからC++もRustも両方ヒープ使ってRVOしてるだけかもしれない
2023/07/03(月) 23:46:31.39ID:uVoNxoEf
ヒープが勝手に使われることはない
ヒープとスタックは空間を通常分けてありアドレス値も見ればわかるくらい異なる値を使っている
単純なローカル変数のアドレス値との比較からスタックのアドレス値かどうかすぐわかる
2023/07/03(月) 23:47:11.84ID:5E3kHz23
>>128
根拠までは示せんが
俺はC/C++とやってきて
関数にnewを直接/間接に呼び出さなずにインスタンスを作れば
スタックに確保されるという認識だな
なのでRVO効いてれば呼び出し側のスタックに作成されるという風に考えている
131デフォルトの名無しさん
垢版 |
2023/07/04(火) 00:03:31.80ID:axzJnblJ
Rustはリターンの場合は多分コピーしてると思いますよ。なぜならサイズの大きい配列の計算において返り値ありときとポインタから直接いじる返り値なしの場合で返り値なしの方が圧倒的に実行速度が速かったから。
2023/07/04(火) 00:16:23.10ID:Z5BWnaiC
推測するな
逆アセンブリ読め
2023/07/04(火) 00:36:27.02ID:+0TfLuMN
少なくとも>>118,>>121ともにRVOが効いててコピーはされてないようには見えますね
ヒープかスタックかは今上がってる実験だけからはなんともいえませんね
この“コンパイラの実装問題”はどんな言語の話でも突っ込んだ話すると必ず出てきますね
言語のregulationで「こういう場合はRVOか働いてコピーはされない、ヒープも使われることなく呼び出し側スタックが確保される事が保証される」みたいな文章が言語のregulationの中に有れば話早いんですけど大概書いてなくてコンパイラの実装依存だったりするしなぁ
2023/07/04(火) 00:36:48.56ID:zEsCcd3F
>>131
コピーしない
>>121のRustコード例のように呼び出し元のスタックフレームへ直接書き込む

もし遅くなっているなら別の要因だろう
たとえば未定義の値を使うことによるバグを一般的に防ぐためにRustは初期化をする
もし初期化をせずとも常に計算値で全て埋めることを保証できれば
unsafeな未初期化を用いてsafeな関数を作り出せる
そして初期化しない分だけ速くなる

もちろん他の要因もありえるためその遅いコードを見ない限り原因はわからない
2023/07/04(火) 01:08:19.71ID:+0TfLuMN
Rustのコンパイラがどういう方法でRVOを実現してるかは多分実装依存で「可能な限りやるようにしろ、プログラマはRVOが働いてくれると期待していい」くらいまでは書いてあるんかもしれないですね
実装としては予想ですけど

・関数のスコープ内でローカルに作られた変数(左辺値?)は原則スタック領域に確保されて関数の処理終了時点で全て解放される
・しかし関数の戻り値として呼び出し側の変数にバインドされて生存期間が伸びた変数は消去されず呼び出し側スタックに残す
・もちろん>>103のような例でコンパイル時点でobject a, object bのどちらを残すか決定できないなら両方残して返り値として使われなかった方のメモリは穴として諦める、どのみちその“呼び出し側の関数”が終了する時点で解放される穴なので諦める

みたいな実装なんでしようかねぇ?
私アセンブラ読めないので確かめられませんけど
2023/07/04(火) 01:50:19.31ID:T/PNhAd4
もし怪しい点があるとすれば>>121のC++側
コンパイルを同じコードに対して最適化レベルを変えて-O0と-O3で比較している
これでは例えば-O3の場合にinlIne化による最適化が起きただけかもしれない
したがってC++側はRVOの証明になっていない

一方>>118のRust側は返す値の大きさの違いで比較して挙動が変わっている
Rust側はRVOの証明になっている
2023/07/04(火) 02:24:25.89ID:H9LDX83D
関数がインライン展開されたら関数間の変数のコピーは省略される
レジスタ変数は参照を取得出来ない
逆にいうと参照を取得したらレジスタではなくメモリ変数(非レジスタ変数)になる

Rustではよく所有権の借用のための参照取得をするけれどそれが
C++ほど最適化されるのでしょうか
2023/07/04(火) 02:44:11.87ID:+0TfLuMN
>>118
の追試

https://ideone.com/6g3Ne2

関数内で2つのFoo型を作ってどっちを返すかコンパイル時には分からない(はす)のコードで実現

fn whichFoo ()-> Foo {
let a = Foo::new(456);
let b = Foo::new(789);
if( true ){
a
}else{
b
}
}
この関数を2回呼んで返り値のアドレスと比較
結果びっくりした

a 456 foo: 0x0x7ffff3242cd0 // 1階目の1項目(返す方)
a 789 foo: 0x0x7ffff3242c98 // 1階目の2項目(捨てる方
fff: 0x0x7ffff3242cd0 // 返り値、1個目と一致
a 456 foo: 0x0x7ffff3242cb0 // 2階目の1項目(返す方)
a 789 foo: 0x0x7ffff3242c98 // 2階目の2項目(捨てる方
fff: 0x0x7ffff3242cb0 // 返り値、1個目と一致

で2回呼んで2回とも1個目のアドレスと一致でRVOが効いてる
のみならず1回目で捨てた方のアドレスが2回目のすてる方のアドレスとして再利用されてる
コレはどうやってんの?
全く予想外
2023/07/04(火) 02:53:28.80ID:ZPezMZV3
そんな気になるなら生成されたコード見たら?
2023/07/04(火) 03:12:41.98ID:w64gVUS7
>>137
最適化される
参照はその値自体を必要としなければ最適化で消え得る

>>138
レジスタ割り当て再利用と同じ
それ以降に使われていなければ領域の再利用の最適化ができる
2023/07/04(火) 03:23:48.80ID:H9LDX83D
>>140
thxです
2023/07/04(火) 07:50:54.82ID:+0TfLuMN
>>141
イヤそれをどうやってんのって話
オレが当然こういうimplementだろうと思ってたのはこう

whichFooが呼ばれてstackが割り当てられる(でもおそらくそのプロセスに割り当てられてるスタックのスタックポインタの上端もらってくるだけだと思うけど)
もらったstackに上から詰めてFoo型のaとbができる
普通に考えて上端から順にaのための領域、bのための領域
この2つは返り値として使用される可能性があるからmainのstackの下端のすぐ下に作ってはおくけどどちらが使われるかは不定

①呼び出し前(♪がsp)
[  未使用域  ]♪| [mainのstack ]
②1回目呼び出し直後
[未使用域]♪[ b ][ a ]| [mainのstack ]

実行時に[a]が返される事が確定、bは破棄されて[a]の真下にspを戻してmainの再開から2回目呼び出されて同じく[a],[b]が作られる

③2回目呼び出し直後
[  未使用域  ]♪[ a ]| mainのstack ]
④2回目の変数領域確保
[未使用域]♪[ b ][a'][ a ]| [mainのstack ]
⑤2回目の変数領域確保
[ 未使用域 ]♪[a'][ a ]| [mainのstack ]

となると予想したら違った
2023/07/04(火) 07:51:01.03ID:+0TfLuMN
今のは運良く最初に確保したaの方が返り値として使われたので簡単に領域bが再利用できるけど、もちろん一般には分からない、なのでコンパイラのできることといえば返り値になるかもしれないbの領域はスタック上端に確保しておいてなるべくフラグメンテーションが起きても被害最小に止めるくらいかなと思ったら違った
(ちなみに残す方と捨てる方逆にしても結果は同じ)
確保されたアドレス見るにコンパイラは最初からaが返されるのを見越して[a]の領域だけをmain側のstackに割り当ててるように見える、1回目も2回目も、そして1回目終了時に回収されたbのためのエリアが再利用されてる
レジスタの場合と徹底的にに違うのはメモリの場合「空いてたら使う」を実現するには“未使用域を完全に把握して新しく領域を確保する時未使用域をなるべく活用する”がかなり難しい事
普通は使用してる下端と未使用の上端の境界にsp置いとくくらいが限界
「使用してるのは①〜②、③〜④、....」なんてできるはずない、できるハズないからフラグメンテーション問題が発生する
もちろんこの“できるはずない”事をやってれば実験結果みたいになって不思議ないんだけど流石に無理じゃないかと
2023/07/04(火) 07:56:44.61ID:+0TfLuMN
でも可能性はあるのかな
プロセス全体で共有されるヒープ全体の使用状況なんか全部管理できないけどローカルの関数に割り当てられたスタックなんてしれてるから使用域、未使用域全部管理してるのかな?
145デフォルトの名無しさん
垢版 |
2023/07/04(火) 09:22:16.43ID:X5jhzPkA
Rustで標準的なチャート生成(グラフ描画)ツールは何ですか?
2023/07/04(火) 10:49:51.72ID:0JerF0m8
>>136,137
なるほどー
Rustはinline展開しないの?
2023/07/04(火) 11:02:31.68ID:XZOUnAWZ
>>142
話は単純
>>138のifでtrueで恒真だから最適化で消えた
2023/07/04(火) 11:18:09.81ID:XZOUnAWZ
>>146
Rustもinline展開される
それとは独立にRustの基本動作として常にRVOが起きる

>>118のコードを
debug mode ← cargo run
release mode ← cargo run -r
の2種類で実行すると

➀debug modeでstruct Foo { a: u64, b: u64, }の場合
 → fooとffiは別アドレス
このdebug modeではinline最適化はされていない
レジスタ返しできる範囲であるため別アドレス

➁release modeでstruct Foo { a: u64, b: u64, }の場合
 → fooとffiは同じアドレス
これはrelease modeでinline最適化がされたため同じアドレスとなった

➂debug modeでstruct Foo { a: u64, b: u64, c: u64 }と大きいサイズの場合
 → fooとffiは同じアドレス
これは➀によりinline最適化はされていない
レジスタ返しできる範囲を超えてるためmain()スタックフレームに直接アクセスしている
2023/07/04(火) 11:53:43.89ID:0JerF0m8
>>121を-fno-inlineをつけてビルドしてみた
$ g++ -O3 -fno-inline -std=c++20 -o test test.cpp
$ ./test
foo: 0x7fffb2b5ee40
fff: 0x7fffb2b5ee40
-fno-inlineでインライン展開されていないとするならRVOが効いている
2023/07/04(火) 13:35:05.06ID:sBWOwVL+
global optimizationがかかったら、どっちにしても、いいようになるのかもしれない
いいようになってくれと思って書くのがいい これはC++もRustも同様
151デフォルトの名無しさん
垢版 |
2023/07/04(火) 15:10:09.42ID:X5jhzPkA
Why you shouldn't learn Rust
https://www.youtube.com/watch?v=kOFWIvNowXo
2023/07/04(火) 15:58:19.26ID:Ot1h7oR2
良いまとめ

Why You Should Learn Rust
https://zerotomastery.io/blog/why-you-should-learn-rust/
2023/07/04(火) 17:54:17.35ID:fS9gKmDv
>>147
せやね
かなぁとは思ってたんやけど時間なくて試せなかった
whichFoo()の条件式を時間のsystem時間の下一桁が奇数か偶数かで決めるように変項したら当たり前みたいにRVOが聞かなくなった

https://ideone.com/T6QJKv

まぁそりゃそうかもね、aかbかどっちが返されるか分からないのに両方とも呼び出し側stackに領域確保してまでRVO効かす事はしないみたいやね
もちろん「フラグメンテーション上等、ローカルスタックに穴開いてもその処理終わるまでなんやから無視、無駄コピーしない事最優先」って実装もできたんやろうけどそうはなってないみたいやな
多分この辺は実装依存なんやろ
154デフォルトの名無しさん
垢版 |
2023/07/04(火) 18:53:05.89ID:axzJnblJ
Rustはなんでsimd命令を安定版で使えないんだろうか。
2023/07/04(火) 19:16:47.25ID:Z5BWnaiC
A-simdラベルつきのissues一通り読んでくれば
2023/07/04(火) 21:51:42.30ID:SetT7x0B
>>145
いろいろある
"Rustでグラフをplotするライブラリのまとめ"
157デフォルトの名無しさん
垢版 |
2023/07/05(水) 01:56:15.57ID:jiFtnYl0
多次元配列の実装ってブロードキャストのところが結構ムズいな。今かなり苦労してるわ。とりあえずnumpyのメジャーな機能はすべて実装したものをrust純正で作りたいか。
2023/07/05(水) 05:24:25.09ID:QQHh6K/1
足りない次元を前から詰めていくだけでは?
それでうまく揃えられなかったらエラー
2023/07/05(水) 08:04:26.42ID:y+DtViwf
rustってマスコットも可愛いし
全てが完璧だよな
2023/07/05(水) 08:08:30.35ID:BYWVWFg9
golang なんてマスコットがきもすぎて皆逃げだしたもんな
2023/07/05(水) 10:16:26.75ID:zDnJAWSM
あれは何周回っても逆に良いとはならないのがすごい
2023/07/05(水) 15:06:58.72ID:5Lz4OcyC
>>145
https://github.com/plotters-rs/plotters
2023/07/05(水) 19:58:33.55ID:p4qutnW6
ʕ◔ϖ◔ʔ < 呼んだ?
164デフォルトの名無しさん
垢版 |
2023/07/05(水) 20:49:18.89ID:jiFtnYl0
とりあえず、Rustで多次元配列の実装自体は大分進んできたけど、配列の値は構造体の中にVec型で持つべきかそれとも他の配列構造体においても同じデータをコピーせずに使い回せるようにするために&Vec型で持たせるべきか悩んでるんだよね。&Vec型だとライフタイムの指定とか複数の可変参照について考えなくちゃいけないからだるいんだけど、一つのデータから複数の配列構造体がコピーなしで作れるんだよな。どうするべきか。
2023/07/05(水) 21:19:29.23ID:Svd9YdDH
Rustの基礎常識
長さを変えうる場合を除いて&Vecや&mut Vecの受け渡しをしない
166デフォルトの名無しさん
垢版 |
2023/07/05(水) 21:46:41.90ID:jiFtnYl0
>>165
配列構造体、ここではNdarray構造体と呼ぶことにする。Ndarray構造体に多次元配列のデータを持たせる。ただし、Ndarray構造体は配列の中身の情報をVec型で保持してるとするこれをdata属性と言うことにする。すると、Ndarray構造体の2つインスタンスに共通のdata属性を持たせることがムズいんですよ。キャッシュの効率的に共通したメモリを占有するdataを持たしたいときがかなりあるのでそこで悩んでいるんですね。
2023/07/05(水) 21:50:12.76ID:drWGyPEK
データ構造体をコードで示すべし
2023/07/05(水) 21:59:42.44ID:SEWIBrGC
なにも考えたくなければRc<[T]>でも使ってれば
2023/07/05(水) 22:02:51.53ID:ZkaWWEhA
一応pytorchの例だと_で終わるメソッドはin-placeで配列を書き換える
170デフォルトの名無しさん
垢版 |
2023/07/05(水) 22:06:34.58ID:jiFtnYl0
Ndarray構造体は簡単に書くと以下のような感じになる。
struct Ndarray<T> {
pub data: Vec<T>,
pub shape: Vec<usize>,
pub stride Vec<usize>,
}
実際のコードではTに関する制約条件も入ってるけどここでは本質的ではないので書かないことにする。
Ndarray構造体を多次元配列の要素に関する情報をdataに持っている。shapeには多次元配列の形状に関する情報が入ってる。ここで、2つのNdarrayインスタンスに同じdataを共有しているような感じにしたいんですよ。勿論、dataのコピーはキャッシュの効率的になしということで。
2023/07/05(水) 22:27:08.29ID:Svd9YdDH
途中で伸び縮みするのか否か
途中で値が書き換わるのか否か
2023/07/05(水) 22:30:04.73ID:QQHh6K/1
いやいや数値計算のライブラリでデフォルト共有とかまずないからコピーで実装してくれ
2023/07/05(水) 22:39:25.32ID:Svd9YdDH
>>164で&Vecを渡そうとしていたから何も書き換わらないと判断すると
コピーするのは無駄
2023/07/06(木) 01:08:51.91ID:sds/6LG1
当面コピーでよくね?
numpyの値受け渡しもcall by valueで毎回コピーしてるんじゃないの?
2023/07/06(木) 01:17:08.65ID:FeGm4Src
コピーするメリットがない
普通に&[T]渡し
2023/07/06(木) 01:38:49.41ID:EXYiMe2p
お前らエロ画像ぶっこ抜くときもRust使ってんの?
2023/07/06(木) 02:02:58.99ID:aaYD2+pH
Rustならエロも安全さ
178デフォルトの名無しさん
垢版 |
2023/07/06(木) 03:18:32.83ID:pKHb2iZt
>>172
計算効率の向上のためですね。よく使うような奴はコピーにする様に動くようにしたいですね。
>>171
途中で伸び縮みはしないと思う。
途中で中身が書きかえられたらすごい都合が良い。出来れば途中で中身が書きかえられるようにしたい。
179デフォルトの名無しさん
垢版 |
2023/07/06(木) 03:21:51.88ID:pKHb2iZt
>>175
Rustは構造体の使用的に属性に参照渡しするのが結構ダルいのではと思っています。正直、自分はライフタイムの`aとかいう感じの修飾子に関してあまり理解できてない
180デフォルトの名無しさん
垢版 |
2023/07/06(木) 03:30:31.36ID:pKHb2iZt
>>174
Numpyはデフォルトでは浅いコピー、要は参照で渡してるっぽいです。自分が調べてみた感じでは。
2023/07/06(木) 04:40:27.15ID:ZBNoJ3oS
スレチですが、
k0 レジスタを、opmask レジスタとして predicate
としては使用できない、ということで正しいのでしょうか?
つまり、
EVEX.512.0F.W0 5E /r
VDIVPS zmm1 {k1}{z}, zmm2, zmm3/m512/m32bcst{er}
と書いてある場合、{k1} の部分は、{k2}や{k3}
などを指定できて、EVEX.aaa の部分には、2や3など、
1〜7 の数値が指定できますが、0 だけは指定できないのでしょう
か? しかし、それだと、k0 のビットが常に全て 1 である
という便利な性質が利用できない気がします。
もしかして、たとえば、k2 レジスタに k0 レジスタの値を
代入してから、{k1}の部分を{k2}と書け、と言うことなんで
しょうか。
だとすれば、なぜ、そんな回りくどい事を。
2023/07/06(木) 05:15:09.86ID:FeGm4Src
>>178
書き換えありの場合
同時共有しないなら単純に&mut [T]渡し
共有するならスレッド内かスレッド間か排他制御の粒度や種類はどうかなどで分かれる
例えばスレッド間で共有しつつ書き込みありで排他制御の粒度はスライス全体で読み込み複数同時可ならばArc<RwLock<&mut [T]>>など
2023/07/06(木) 06:11:31.90ID:FeGm4Src
すまん&mutはもちろん不要
Arc<RwLock<[T]>> ね
配列からでなくVecからなら
Arc<RwLock<Vec<T>>> か
長さ変わらないならBox化して
Arc<RwLock<Box<[T]>>>
2023/07/06(木) 08:06:47.31ID:pjKGVGBZ
>>179
それは例えると
「私はC言語を書けますがポインタを使えません」
と言ってるのと同じでマズいな
2023/07/06(木) 12:21:28.35ID:zocpc7Jo
>>164
lifetime 付けたくないなら
struct Ndarray<T> {
pub data: Option<&Vec<T>>,
pub shape: Option<&Vec<usize>>,
pub stride Option<&Vec<usize>>,
}
2023/07/06(木) 12:24:16.16ID:zocpc7Jo
>>176
初めてのRustはエロ収集scrapingツールだった
2023/07/06(木) 14:30:31.20ID:aQspz1Zu
ネタにマジレスですまんが、スクレーピングみたいのってRust得意なんけ
188デフォルトの名無しさん
垢版 |
2023/07/06(木) 14:53:35.46ID:3Kmo4orY
得意かは知らないが非同期io機能が充実しててやりやすい
2023/07/06(木) 17:09:56.98ID:rXBnP+Dg
rustに全てを置いてきた
2023/07/06(木) 21:21:05.78ID:fNr5Fd/6
>>185
ないわw
2023/07/06(木) 21:34:07.17ID:24F0wnkd
シャローコピーってスライス代入と普通の代入の時だけでしょ?
ディープコピーは...演算子を使う
さらにin-place演算子(+=など)は変数を書き換える
それ以外は全てコピーを作って返す
これがnumpy
2023/07/06(木) 21:39:43.66ID:24F0wnkd
ndarrayはスライスをマクロで定義している
さらにスライスはArrayViewを返す
これは真似た方が良い
あとはslice_mut...assignという書き方を踏襲するかどうか
193デフォルトの名無しさん
垢版 |
2023/07/07(金) 02:03:56.84ID:dzAVAX7p
>>192
自分は初学者でも分かりやすいように気本的に配列に関する全ての操作はNdarray構造体に押し込みたいんだよね。
2023/07/07(金) 03:34:18.30ID:LxI5CxNo
ダメそう
195デフォルトの名無しさん
垢版 |
2023/07/07(金) 04:58:02.59ID:puo7kNQ2
オプソのするつもりならRustに反対しないが
オプソにするつもりの無いプロジェクトなら
迷わずC/C++ジャマイカ?
196デフォルトの名無しさん
垢版 |
2023/07/07(金) 11:05:05.39ID:dzAVAX7p
RustでNumpyライクなクレートを制作中だけど、ある程度形になったら公開するつもりだよ。OSSにするつもりだし。
2023/07/07(金) 12:30:09.61ID:98SFipJR
>>193
rust使いに初心者がいるとは思えないのだが
ということはやはりRcで持つということになるんかね
chainerのarrayの実装もshared_ptrで持ってる
https://github.com/chainer/chainer/blob/master/chainerx_cc/chainerx/array_body.h
2023/07/07(金) 13:01:06.40ID:8V8sPKbL
まぁでも実際には難しいやろな
かつてプログラミングの世界を席巻していたPascalを駆逐してCが覇権を握ったのは多分基本的な関数の呼び出し方式をcall by refference からcall by valueね変更したのが大きい
cpuの性能が上がるにつれ整数型とか浮動小数点型の受け渡し時程度なら毎回値をコピーする実装にしてもそれで落ちるパフォーマンスの低下はやはりcall by valueの持つ“読みやすさ、組みやすさ”の優位性が際立ってきた
でもとはいえ毎回呼び出しのたびに値をコピーするcall by valueに全統一するのは無理なので文字列やベクトル処理についてはcall by refferenceをえらべるようなよくいえば柔軟、悪くいえば場当たり的な実装にしたのがCが覇権を握った最大の要因かもしれない
しかし今日さらにプログラミング言語の研究が進んでRVO技術などを利用して"call by valueの文法なのにコピーしない"という事が可能になってきて話が違ってきたのは事実、この先この技術がさらに発展すれば“call by valueを使わないベクトル処理ライブラリ”なんてものが出てきてもおかしくはない
でもこのRVOはまだまだ使いにくい、Rustの資料色々読んでも「関数の戻り値の受け渡しの際必ずしもコピーが発生するわけではない」程度の話しか出てこない、この辺は今現在開発がどんどん進んで行っててまだまだ“企画”としてまとめられる段階にないんやろ
だとすると現状“ベクトル処理ライブラリ”をまとめるならcall by refferenceを使う他ない
問題はその際発生するlife time注釈をどうするかの問題、しかしコレもRustの版が進むにつれ変化している“開発途上”の立ち位置で“注釈の省略”の機能を了して“一才注釈なしで使える”はちょっと難しいかも
199デフォルトの名無しさん
垢版 |
2023/07/07(金) 13:05:19.96ID:ESNGSxIs
>>197
初心者がRustに参入しやすくなるようなライブラリを作ることが目標だから。私自身も初心者に毛が生えたようなレベルだし。
同じ可変参照を持つ構造体のインスタンスが複数作られることを考慮してるからRxとかArcを使うことになる可能性は高い。
2023/07/07(金) 13:17:59.91ID:98SFipJR
いや高いじゃなくてw
■ このスレッドは過去ログ倉庫に格納されています
5ちゃんねるの広告が気に入らない場合は、こちらをクリックしてください。

ニューススポーツなんでも実況