結局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/02(日) 22:49:45.19ID:OihXi6HP
>>76
並行プログラミングの基本はOSをノンブロッキングで呼び出すところにある
例えばシングルスレッドでもファイルを読み書きしつつ10000個のクライアントと通信するサーバーも作れる
これが並行プログラミング
具体的には>>77のepoll等やあるいはそれらを用いてマルチプラットフォーム対応したlibuvライブラリ等を使う

>>78
Node.js以前から行なわれてきている
Node.jsはそのイベントループ構造部分を内蔵していて
その核心部分をプログラミングできない人でもJavaScriptを書くだけで使える点は大きいね
2023/07/02(日) 22:54:55.66ID:aMd0Z/JA
C++にもco_awaitきてるけど、ああいうのこそ、所有権とか来たらラクに書けるだろうな
コルーチンおもろいぞ、C++erもいっぺん経験してみるべき
2023/07/02(日) 22:59:35.55ID:mbYcfrVi
フロントエンドならRustが活躍できるんだな
2023/07/02(日) 23:07:43.48ID:FpcWXL3u
いいGUIライブラリを使いなさい
2023/07/02(日) 23:09:15.57ID:OihXi6HP
Rustは昔からmioクレートがマルチプラットフォーム対応していて
| OS | Selector |
|---------------|-----------|
| Android | [epoll] |
| DragonFly BSD | [kqueue] |
| FreeBSD | [kqueue] |
| iOS | [kqueue] |
| illumos | [epoll] |
| Linux | [epoll] |
| NetBSD | [kqueue] |
| OpenBSD | [kqueue] |
| Windows | [IOCP] |
| macOS | [kqueue] |
こんな感じ

現在はmioを直接使わなくても
mioの上に並行並列スケジューラを構築しているtokioを使えばよい
2023/07/02(日) 23:47:03.38ID:jzpBLYtw
>>79
ファイルの話じゃなく描画の話をしてるんだが
2023/07/02(日) 23:57:38.16ID:aMd0Z/JA
最近ちょっと(GUIまわりから)離れてるけど、GUIのメッセージキューがメインスレッドに付いてるから、
処理をとっととワーカスレッドに移しちゃうってのは、古くて新しいテーマだよな
2023/07/03(月) 00:14:59.36ID:uVoNxoEf
>>84
ファイルだろうがネット通信だろうが描画だろうが全てソケットに抽象化されているので同じ
2023/07/03(月) 00:21:47.27ID:f710ZASI
C言語が、あるいはそれに連なるC++が業界標準の地位を確立したのって何ともANSI Cの存在が大きい気がします
言語が流行るためには何はともあれ“人が作ったプログラムが自分の環境で試せる”と言うのは大きい。それをちょっと変えてみたらどうなるかとか試してみて学んだりできるのって大きい気がします
今の“業界標準のRust”ってどこが取り仕切っているんですか?
そしてその“業界標準Rust”に収録されてる“標準ライブラリ”の一覧のようなものはどこかで一覧の形で見れますか?、
2023/07/03(月) 00:25:47.65ID:t+8O8Mfo
epollって並列待機処理でそもそもの待機側を開放したい
async/awaitとは別の話題だと思うんだが
2023/07/03(月) 01:22:18.45ID:5E3kHz23
>>87
https://www.rust-lang.org/ja/governance

Rustユーザがもっと増えて
gccrsのように他にもコンパイラが出てきたら
規格が必要になってくるかもね
2023/07/03(月) 01:36:25.99ID:uVoNxoEf
>>88
同じ
(スレッドを使う並列でななく)並行処理を行なうコードを自分で書いてみるとわかる
I/Oを非同期多重化することになる
ためselect/poll/epoll/kqueueを用いたイベントループがスケジューラ相当として核をなす
それを直接使う未分化な原始的な利用から始めて、
次にコールバック方式による単純分離、
さらにpromise/futureによる抽象化、
そしてasync/awaitによる糖衣構文、
と進むことになるが核部分はもちろん同じ
2023/07/03(月) 01:37:43.68ID:7CVvhqQm
>>89
thx
92デフォルトの名無しさん
垢版 |
2023/07/03(月) 02:06:46.42ID:bFZG83Bf
サイズの大きい配列って結構コピーの部分で時間が取られるのかねえ?GUI周りの話ではないが。Rustでnumpyライクなライブラリを作っているからそこら辺は気になる。
2023/07/03(月) 02:28:24.34ID:+YxiNHjZ
>>88
async/awaitって元はただのブロックしないコールバック付きの関数だよ
2023/07/03(月) 03:05:22.10ID:3IqJ4QRg
と言うかRustの利点はそこにあるんやろ
特に返り値のとき
返り値に大きいサイズのObjectを返すときが問題
その場合Cでは返り値のどデカい構造体を呼び出し側のスタック領域にコピーする手間がどうしてもかかってしまう
それを避けるには
・返り値をヒープに領域確保して返す
・呼び出し側で受け取りようの空の構造体領域を呼び出し側スタックエリアに確保してその参照を渡して返り値をそこに書き込んでもらう
くらいしかない
しかしヒープにわざわざ永続的には存在する必要のないデータの受け渡しのためエリアをとるとフラグメンテーションの原因になるし、呼び出し側が返り値の大きさをあらかじめ計算してから領域確保して呼び出さないといけないのはメチャクチャ手間がかかってしまう
Rustなら返り値の構造体をダイレクトに呼び出し側のスタックエリアに確保できる(と思う、未確認)のでそこでパフォーマンスの優位性が出る(ハズ)
今のC,C++にはこのメカニズムを実現する方法はないと思う
2023/07/03(月) 03:22:41.96ID:+YxiNHjZ
>>94
別にそこに関しては今やRustもC++も大して変わらんぞ
今のC++はreturn時に余計なコピーをしないようになってる
2023/07/03(月) 03:24:05.61ID:5E3kHz23
RVO?
2023/07/03(月) 03:37:51.42ID:80Y4MX+q
>>95
そうなんだ
CとかC++は必ずコピーが発生するからどうやって回避するかでの定石習ったけどな
今はコピー回避できるんだ
2023/07/03(月) 04:29:45.61ID:+YxiNHjZ
>>97
今のC++コンパイラはめちゃくちゃ賢くなってて
最適化できる処理はほぼ全て最適化する
特にreturn時や引数に渡す場合の処理で最適化して問題ないと判断されるとほぼ最適化される
2023/07/03(月) 04:35:39.10ID:+YxiNHjZ
>>92
めちゃくちゃ遅くなるよ
現代のアーキテクチャでは無駄なアロケーションやコピーが1番遅くなる要因
1番速いのは当然同じ領域を使い回してそこに書き込むようにすること
キャッシュも効くし無駄なアロケーションが発生しない
ただし副作用と可読性の低下という犠牲を払う
速度と安全性はまさに水と油
2023/07/03(月) 04:46:33.55ID:mO9TuvlK
>>98
その“コピー回避”の方法は“ヒープを利用して回避”ではなくて“呼び出し側のスタック領域を利用する”で回避してるんですか?
ソースあります?
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
途中で伸び縮みはしないと思う。
途中で中身が書きかえられたらすごい都合が良い。出来れば途中で中身が書きかえられるようにしたい。
■ このスレッドは過去ログ倉庫に格納されています