「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/
探検
結局C++とRustってどっちが良いの? 5traits
■ このスレッドは過去ログ倉庫に格納されています
2023/06/30(金) 21:56:35.52ID:PDIJ4aZy
2023/07/02(日) 15:49:02.90ID:OihXi6HP
TauriやElectronはGUIといっても
・マルチプラットフォームでスマホ対応も進行中
・Webと同じ知識技術で作れる
・Webアプリへとの共有や移行が容易
と大きなメリットがあり
VSCodeなどこの方式で成功しているアプリが多数ある
・マルチプラットフォームでスマホ対応も進行中
・Webと同じ知識技術で作れる
・Webアプリへとの共有や移行が容易
と大きなメリットがあり
VSCodeなどこの方式で成功しているアプリが多数ある
2023/07/02(日) 15:52:44.86ID:nM9kee/9
Rustで全てが非同期かつコンポーネントベースのGUIライブラリを作る機運が生まれてる
描画から全部自前でやる本当のネイティブ版Reactのようなもの
描画から全部自前でやる本当のネイティブ版Reactのようなもの
2023/07/02(日) 18:19:31.18ID:owOvhd2B
GUIをC++やRustでやる意味とは
UEだけだろ意味あるの
UEだけだろ意味あるの
2023/07/02(日) 18:40:53.06ID:nM9kee/9
2023/07/02(日) 19:45:01.65ID:l4k0OEWm
すべてasyncとか死ぬほどめんどくさそう
2023/07/02(日) 19:49:14.43ID:T91CalxQ
いやGUIだとそれが普通なんだよ
UIスレッドでブロッキングAPI使えないのだからその設計が普通
今までのライブラリの設計がミスってた
UIスレッドでブロッキングAPI使えないのだからその設計が普通
今までのライブラリの設計がミスってた
2023/07/02(日) 19:55:13.31ID:T91CalxQ
このライブラリがかなり理想に近い
https://wishawa.github.io/posts/async-ui-intro/
見てもらえればわかるがほぼReactだ
しかも全てが非同期なので非常に速い
https://wishawa.github.io/posts/async-ui-intro/
見てもらえればわかるがほぼReactだ
しかも全てが非同期なので非常に速い
71デフォルトの名無しさん
2023/07/02(日) 20:01:17.15ID:OihXi6HP そもそもJavaScriptが非同期に動き並行プログラミングだからね
2023/07/02(日) 21:45:39.87ID:aMd0Z/JA
C#もそんな感じだろ
メッセージポンプスレを止めちゃいけないからとか聞いた、そういやそうだよな
モダンだね
メッセージポンプスレを止めちゃいけないからとか聞いた、そういやそうだよな
モダンだね
2023/07/02(日) 21:52:34.41ID:T91CalxQ
C#は残念ながらブロックする処理を書く場合はスレッドを立てなきゃいけなくて遅い
さらにUIスレッドへの同期コンテキストが必要でその分さらに遅い
さらにUIスレッドへの同期コンテキストが必要でその分さらに遅い
2023/07/02(日) 21:59:34.59ID:jzpBLYtw
結局のところOSの描画API呼び出すから、
最終的にUIスレッドに描画任せないといけないのはどのライブラリでも変わらん
最終的にUIスレッドに描画任せないといけないのはどのライブラリでも変わらん
2023/07/02(日) 22:04:50.40ID:OihXi6HP
>>74
それは違うんじゃないか
例えばファイル読み書きもOS呼び出すけど
並行プログラミングができていれば
シングルスレッドでもファイル読み書きでブロックされず
待ち時間に他の作業をできるよ
そこにマルチスレッドや描画専用スレッドの必要性はない
それは違うんじゃないか
例えばファイル読み書きもOS呼び出すけど
並行プログラミングができていれば
シングルスレッドでもファイル読み書きでブロックされず
待ち時間に他の作業をできるよ
そこにマルチスレッドや描画専用スレッドの必要性はない
2023/07/02(日) 22:08:24.86ID:jzpBLYtw
OSの仕組み理解してなさそう
2023/07/02(日) 22:27:06.93ID:ajw2MJiV
2023/07/02(日) 22:31:37.38ID:T91CalxQ
なぜ非同期が良いか?
UIスレッドが呼び出しても何の問題もないことが保証されてるから
これはnode.jsが気が付かせてくれたパラダイムシフトだよ
UIスレッドが呼び出しても何の問題もないことが保証されてるから
これはnode.jsが気が付かせてくれたパラダイムシフトだよ
2023/07/02(日) 22:49:45.19ID:OihXi6HP
2023/07/02(日) 22:54:55.66ID:aMd0Z/JA
C++にもco_awaitきてるけど、ああいうのこそ、所有権とか来たらラクに書けるだろうな
コルーチンおもろいぞ、C++erもいっぺん経験してみるべき
コルーチンおもろいぞ、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を使えばよい
| 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”に収録されてる“標準ライブラリ”の一覧のようなものはどこかで一覧の形で見れますか?、
言語が流行るためには何はともあれ“人が作ったプログラムが自分の環境で試せる”と言うのは大きい。それをちょっと変えてみたらどうなるかとか試してみて学んだりできるのって大きい気がします
今の“業界標準のRust”ってどこが取り仕切っているんですか?
そしてその“業界標準Rust”に収録されてる“標準ライブラリ”の一覧のようなものはどこかで一覧の形で見れますか?、
2023/07/03(月) 00:25:47.65ID:t+8O8Mfo
epollって並列待機処理でそもそもの待機側を開放したい
async/awaitとは別の話題だと思うんだが
async/awaitとは別の話題だと思うんだが
2023/07/03(月) 01:22:18.45ID:5E3kHz23
2023/07/03(月) 01:36:25.99ID:uVoNxoEf
>>88
同じ
(スレッドを使う並列でななく)並行処理を行なうコードを自分で書いてみるとわかる
I/Oを非同期多重化することになる
ためselect/poll/epoll/kqueueを用いたイベントループがスケジューラ相当として核をなす
それを直接使う未分化な原始的な利用から始めて、
次にコールバック方式による単純分離、
さらにpromise/futureによる抽象化、
そしてasync/awaitによる糖衣構文、
と進むことになるが核部分はもちろん同じ
同じ
(スレッドを使う並列でななく)並行処理を行なうコードを自分で書いてみるとわかる
I/Oを非同期多重化することになる
ためselect/poll/epoll/kqueueを用いたイベントループがスケジューラ相当として核をなす
それを直接使う未分化な原始的な利用から始めて、
次にコールバック方式による単純分離、
さらにpromise/futureによる抽象化、
そしてasync/awaitによる糖衣構文、
と進むことになるが核部分はもちろん同じ
2023/07/03(月) 01:37:43.68ID:7CVvhqQm
>>89
thx
thx
92デフォルトの名無しさん
2023/07/03(月) 02:06:46.42ID:bFZG83Bf サイズの大きい配列って結構コピーの部分で時間が取られるのかねえ?GUI周りの話ではないが。Rustでnumpyライクなライブラリを作っているからそこら辺は気になる。
2023/07/03(月) 02:28:24.34ID:+YxiNHjZ
>>88
async/awaitって元はただのブロックしないコールバック付きの関数だよ
async/awaitって元はただのブロックしないコールバック付きの関数だよ
2023/07/03(月) 03:05:22.10ID:3IqJ4QRg
と言うかRustの利点はそこにあるんやろ
特に返り値のとき
返り値に大きいサイズのObjectを返すときが問題
その場合Cでは返り値のどデカい構造体を呼び出し側のスタック領域にコピーする手間がどうしてもかかってしまう
それを避けるには
・返り値をヒープに領域確保して返す
・呼び出し側で受け取りようの空の構造体領域を呼び出し側スタックエリアに確保してその参照を渡して返り値をそこに書き込んでもらう
くらいしかない
しかしヒープにわざわざ永続的には存在する必要のないデータの受け渡しのためエリアをとるとフラグメンテーションの原因になるし、呼び出し側が返り値の大きさをあらかじめ計算してから領域確保して呼び出さないといけないのはメチャクチャ手間がかかってしまう
Rustなら返り値の構造体をダイレクトに呼び出し側のスタックエリアに確保できる(と思う、未確認)のでそこでパフォーマンスの優位性が出る(ハズ)
今のC,C++にはこのメカニズムを実現する方法はないと思う
特に返り値のとき
返り値に大きいサイズのObjectを返すときが問題
その場合Cでは返り値のどデカい構造体を呼び出し側のスタック領域にコピーする手間がどうしてもかかってしまう
それを避けるには
・返り値をヒープに領域確保して返す
・呼び出し側で受け取りようの空の構造体領域を呼び出し側スタックエリアに確保してその参照を渡して返り値をそこに書き込んでもらう
くらいしかない
しかしヒープにわざわざ永続的には存在する必要のないデータの受け渡しのためエリアをとるとフラグメンテーションの原因になるし、呼び出し側が返り値の大きさをあらかじめ計算してから領域確保して呼び出さないといけないのはメチャクチャ手間がかかってしまう
Rustなら返り値の構造体をダイレクトに呼び出し側のスタックエリアに確保できる(と思う、未確認)のでそこでパフォーマンスの優位性が出る(ハズ)
今のC,C++にはこのメカニズムを実現する方法はないと思う
2023/07/03(月) 03:22:41.96ID:+YxiNHjZ
2023/07/03(月) 03:24:05.61ID:5E3kHz23
RVO?
2023/07/03(月) 03:37:51.42ID:80Y4MX+q
2023/07/03(月) 04:29:45.61ID:+YxiNHjZ
2023/07/03(月) 04:35:39.10ID:+YxiNHjZ
>>92
めちゃくちゃ遅くなるよ
現代のアーキテクチャでは無駄なアロケーションやコピーが1番遅くなる要因
1番速いのは当然同じ領域を使い回してそこに書き込むようにすること
キャッシュも効くし無駄なアロケーションが発生しない
ただし副作用と可読性の低下という犠牲を払う
速度と安全性はまさに水と油
めちゃくちゃ遅くなるよ
現代のアーキテクチャでは無駄なアロケーションやコピーが1番遅くなる要因
1番速いのは当然同じ領域を使い回してそこに書き込むようにすること
キャッシュも効くし無駄なアロケーションが発生しない
ただし副作用と可読性の低下という犠牲を払う
速度と安全性はまさに水と油
100デフォルトの名無しさん
2023/07/03(月) 04:46:33.55ID:mO9TuvlK101デフォルトの名無しさん
2023/07/03(月) 04:48:48.70ID:+YxiNHjZ102デフォルトの名無しさん
2023/07/03(月) 04:56:04.09ID:+YxiNHjZ prvalueとかxvalueとかの値のカテゴリについてはこちらが詳しい
https://cpp.rainy.me/037-rvalue-reference.html#%E6%A6%82%E8%A6%81
https://cpp.rainy.me/037-rvalue-reference.html#%E6%A6%82%E8%A6%81
103デフォルトの名無しさん
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ならできるのかはまだ勉強中なのでよく分からないですけど
thx
でもコレやっぱりこの機能をスタック領域でやってるのかどうかの明言はないですね
上の方で誰かが言ってたんですけどC++のスマポ絡みは全部ヒープでやってるって話があったのでどうなんだろと
この問題はとても難しくて私勉強してた頃は解決できてなかったんですよ
例えば
object theFunc(){
object a,b;
.......
if comp( a, b) {
return( a );
} else {
return( b );
}
}
のようなコードの場合コンパイラは実際に変数aかbかのどちらが返り値として返されるのか決定する事はできません
もうこのような場合にはほとんど必然的にコピーするしかなくなります
回避する唯一の方法はobject a,bの両方を呼び出し側のスタックに確保して使わなかった方のobjectは呼び出し側スタックの穴として我慢します
もちろん一時的にスタックに穴が開きますがその呼び出し側の処理が終われば全部御破算にされるので目を瞑る作戦です
なんか聞こえは悪いですがスタック領域中に使われる変数でほんの一瞬しか使われない変数なんか山のように出るものなのでそれを一々気にしてたらキリがありません、問題視しないといけないのは永続的に残るヒープ領域のフラグメンテーションです
じゃあいつでも何でもかんでも呼び出し側のスタックに返り値を受け取る領域確保すればいいのかといえばそれもウソでほんの数バイトの情報しかないならいる分だけコピーしていらないデータは綺麗さっぱり消してしまった方がいい時もあって、どちらが良いかコンパイラは判断する方法がCでは中々解決できないみたいな事習った記憶があります
Rustならできるのかはまだ勉強中なのでよく分からないですけど
104デフォルトの名無しさん
2023/07/03(月) 06:42:22.66ID:YLpV4/YA スタックの穴ってなんのことぞや
関数等の行き返りで、intptr_t以上のものを受け渡ししようっていうのが、そもそもの無理筋感があるんだよな
rvalueを渡すなんていうのは、渡しているのやら、いないのやら
C++時代は、自信のないことはしない、だったんだ
関数等の行き返りで、intptr_t以上のものを受け渡ししようっていうのが、そもそもの無理筋感があるんだよな
rvalueを渡すなんていうのは、渡しているのやら、いないのやら
C++時代は、自信のないことはしない、だったんだ
105デフォルトの名無しさん
2023/07/03(月) 06:49:45.31ID:YLpV4/YA106デフォルトの名無しさん
2023/07/03(月) 08:24:46.39ID:Pxa6JMwc 最適化しすぎるが故に、未定義を踏むとこういう事も起きてしまうという
https://cpplover.blogspot.com/2014/06/old-new-thing.html
https://cpplover.blogspot.com/2014/06/old-new-thing.html
107デフォルトの名無しさん
2023/07/03(月) 08:42:15.29ID:bFZG83Bf 何かRustだとreturn時にコピーが行われてるような気がする。
108デフォルトの名無しさん
2023/07/03(月) 08:58:28.64ID:gVPXZxAL std::move() もそうだが、最低限のコピーはしないといかんぞどうせ
生成コードみてみろよ C++ならそうするので、取るべき行動は同じ
生成コードみてみろよ C++ならそうするので、取るべき行動は同じ
109デフォルトの名無しさん
2023/07/03(月) 09:05:18.13ID:XqdEtb// 要はAddAssignとAddだと計算結果は同じでもAddAssignの方が圧倒的に速いという現象が起こったりすることがある。これはAddだとコピーをリターンするのに対してAddAssignだとselfの値は同じメモリ上で書きかえられてreturnによるコピーがないから。
110デフォルトの名無しさん
2023/07/03(月) 09:32:31.64ID:XuaWdgM7 確かに WideCharToMultiByte/MultiByteToWideChar は毎回
↓
容量確認で1回目呼び出し
↓
確保して
↓
容量指定して2回目呼び出し
↓
面倒だと思ってた
↓
容量確認で1回目呼び出し
↓
確保して
↓
容量指定して2回目呼び出し
↓
面倒だと思ってた
111デフォルトの名無しさん
2023/07/03(月) 09:54:04.75ID:gVPXZxAL 毎回書くからだろ、ライブラリ(コードスニペット)にしちゃえw
112デフォルトの名無しさん
2023/07/03(月) 10:05:14.03ID:kK4jJ7eL コンパイラが吐き出すコードなんて意図通りにならない
疑い深いなら細かくチェックしながらアセンブラで書くしかない
ループ内の重要な変数はレジスタを使っているか
無駄なコピーが無いか
ループ部分に無駄な関数呼びだしが無いか
実際のメモリに読み書きしやすい順序でデータが並んでいるか
最初に必要なメモリを一括でOSから取得しているか
最適な命令を使っているか
キャッシュ汚染が無いか
疑い深いなら細かくチェックしながらアセンブラで書くしかない
ループ内の重要な変数はレジスタを使っているか
無駄なコピーが無いか
ループ部分に無駄な関数呼びだしが無いか
実際のメモリに読み書きしやすい順序でデータが並んでいるか
最初に必要なメモリを一括でOSから取得しているか
最適な命令を使っているか
キャッシュ汚染が無いか
113デフォルトの名無しさん
2023/07/03(月) 10:10:50.88ID:gVPXZxAL アセンブラで書いてすら、意図通りにならないくらいに思っとけってばっちゃんが言ってた
(適当に分割して、複数パイプラインに流し込まれたりするから)
(適当に分割して、複数パイプラインに流し込まれたりするから)
114デフォルトの名無しさん
2023/07/03(月) 11:59:58.23ID:fwYpLKgv >>96
RVOだよなぁ
RVOだよなぁ
115デフォルトの名無しさん
2023/07/03(月) 16:06:04.98ID:+YxiNHjZ116デフォルトの名無しさん
2023/07/03(月) 16:10:20.37ID:+YxiNHjZ このようにC++は色々複雑すぎてもはや普通の人が理解できるものではなくなったので
素直にrust使おうよという振りなんだけどね
素直にrust使おうよという振りなんだけどね
117デフォルトの名無しさん
2023/07/03(月) 16:16:32.89ID:5E3kHz23 Rustだとどうなるの? ソースで例示プリーズ
118デフォルトの名無しさん
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
#[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
119デフォルトの名無しさん
2023/07/03(月) 17:12:42.36ID:uVoNxoEf 前者のアドレスが異なる場合というのは
レジスタ上だけで済むところを敢えてアドレス表示させているため
実際にはスタックを使わずレジスタのみの利用に成りうることを意味してることに注意
レジスタ上だけで済むところを敢えてアドレス表示させているため
実際にはスタックを使わずレジスタのみの利用に成りうることを意味してることに注意
120デフォルトの名無しさん
2023/07/03(月) 18:59:43.08ID:MgkFcxqO なるほどrustは呼び出し元スタックに直接値返せるのね
コレはC++でできるんですか?
コレはC++でできるんですか?
121デフォルトの名無しさん
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
#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
122デフォルトの名無しさん
2023/07/03(月) 21:12:04.60ID:MgkFcxqO >>121
それは呼び出し側のスタックですか?
それは呼び出し側のスタックですか?
123デフォルトの名無しさん
2023/07/03(月) 21:12:23.79ID:5E3kHz23 NRVOなのでいつも効くとは限らない?
124デフォルトの名無しさん
2023/07/03(月) 21:15:14.72ID:5E3kHz23 >>122
0x7fff0bbf3560と0x7ffed05f53a0は呼び出し側のスタックなのでは?
0x7fff0bbf3560と0x7ffed05f53a0は呼び出し側のスタックなのでは?
125デフォルトの名無しさん
2023/07/03(月) 22:09:08.78ID:+q7z5VN1 たったそれだけのことを
ヒープから確保してガベージコレクションで解放する重い遅いプログラミング言語がほとんどの中
C++とRustが優秀すぎるハイレベルな戦い
ヒープから確保してガベージコレクションで解放する重い遅いプログラミング言語がほとんどの中
C++とRustが優秀すぎるハイレベルな戦い
126デフォルトの名無しさん
2023/07/03(月) 23:25:23.71ID:MgkFcxqO127デフォルトの名無しさん
2023/07/03(月) 23:28:41.59ID:MgkFcxqO あ、いや、今ググったらC++でもRVO実現してるみたいですね
失礼しました
しかし本当にそのアドレスの数値が一致していることだけでRVOが効いてる事の証明にはならないとは思います
失礼しました
しかし本当にそのアドレスの数値が一致していることだけでRVOが効いてる事の証明にはならないとは思います
128デフォルトの名無しさん
2023/07/03(月) 23:31:27.59ID:MgkFcxqO あ、よくよく読んだら「RVOとは戻り値返す時にコピーしない技術のこと」であって「呼び出し側のスタックを利用するかどうか」は別問題のようですね
まぁだからC++もRustも両方ヒープ使ってRVOしてるだけかもしれない
まぁだからC++もRustも両方ヒープ使ってRVOしてるだけかもしれない
129デフォルトの名無しさん
2023/07/03(月) 23:46:31.39ID:uVoNxoEf ヒープが勝手に使われることはない
ヒープとスタックは空間を通常分けてありアドレス値も見ればわかるくらい異なる値を使っている
単純なローカル変数のアドレス値との比較からスタックのアドレス値かどうかすぐわかる
ヒープとスタックは空間を通常分けてありアドレス値も見ればわかるくらい異なる値を使っている
単純なローカル変数のアドレス値との比較からスタックのアドレス値かどうかすぐわかる
130デフォルトの名無しさん
2023/07/03(月) 23:47:11.84ID:5E3kHz23 >>128
根拠までは示せんが
俺はC/C++とやってきて
関数にnewを直接/間接に呼び出さなずにインスタンスを作れば
スタックに確保されるという認識だな
なのでRVO効いてれば呼び出し側のスタックに作成されるという風に考えている
根拠までは示せんが
俺はC/C++とやってきて
関数にnewを直接/間接に呼び出さなずにインスタンスを作れば
スタックに確保されるという認識だな
なのでRVO効いてれば呼び出し側のスタックに作成されるという風に考えている
131デフォルトの名無しさん
2023/07/04(火) 00:03:31.80ID:axzJnblJ Rustはリターンの場合は多分コピーしてると思いますよ。なぜならサイズの大きい配列の計算において返り値ありときとポインタから直接いじる返り値なしの場合で返り値なしの方が圧倒的に実行速度が速かったから。
132デフォルトの名無しさん
2023/07/04(火) 00:16:23.10ID:Z5BWnaiC 推測するな
逆アセンブリ読め
逆アセンブリ読め
133デフォルトの名無しさん
2023/07/04(火) 00:36:27.02ID:+0TfLuMN134デフォルトの名無しさん
2023/07/04(火) 00:36:48.56ID:zEsCcd3F135デフォルトの名無しさん
2023/07/04(火) 01:08:19.71ID:+0TfLuMN Rustのコンパイラがどういう方法でRVOを実現してるかは多分実装依存で「可能な限りやるようにしろ、プログラマはRVOが働いてくれると期待していい」くらいまでは書いてあるんかもしれないですね
実装としては予想ですけど
・関数のスコープ内でローカルに作られた変数(左辺値?)は原則スタック領域に確保されて関数の処理終了時点で全て解放される
・しかし関数の戻り値として呼び出し側の変数にバインドされて生存期間が伸びた変数は消去されず呼び出し側スタックに残す
・もちろん>>103のような例でコンパイル時点でobject a, object bのどちらを残すか決定できないなら両方残して返り値として使われなかった方のメモリは穴として諦める、どのみちその“呼び出し側の関数”が終了する時点で解放される穴なので諦める
みたいな実装なんでしようかねぇ?
私アセンブラ読めないので確かめられませんけど
実装としては予想ですけど
・関数のスコープ内でローカルに作られた変数(左辺値?)は原則スタック領域に確保されて関数の処理終了時点で全て解放される
・しかし関数の戻り値として呼び出し側の変数にバインドされて生存期間が伸びた変数は消去されず呼び出し側スタックに残す
・もちろん>>103のような例でコンパイル時点でobject a, object bのどちらを残すか決定できないなら両方残して返り値として使われなかった方のメモリは穴として諦める、どのみちその“呼び出し側の関数”が終了する時点で解放される穴なので諦める
みたいな実装なんでしようかねぇ?
私アセンブラ読めないので確かめられませんけど
136デフォルトの名無しさん
2023/07/04(火) 01:50:19.31ID:T/PNhAd4137デフォルトの名無しさん
2023/07/04(火) 02:24:25.89ID:H9LDX83D 関数がインライン展開されたら関数間の変数のコピーは省略される
レジスタ変数は参照を取得出来ない
逆にいうと参照を取得したらレジスタではなくメモリ変数(非レジスタ変数)になる
Rustではよく所有権の借用のための参照取得をするけれどそれが
C++ほど最適化されるのでしょうか
レジスタ変数は参照を取得出来ない
逆にいうと参照を取得したらレジスタではなくメモリ変数(非レジスタ変数)になる
Rustではよく所有権の借用のための参照取得をするけれどそれが
C++ほど最適化されるのでしょうか
138デフォルトの名無しさん
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回目のすてる方のアドレスとして再利用されてる
コレはどうやってんの?
全く予想外
の追試
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回目のすてる方のアドレスとして再利用されてる
コレはどうやってんの?
全く予想外
139デフォルトの名無しさん
2023/07/04(火) 02:53:28.80ID:ZPezMZV3 そんな気になるなら生成されたコード見たら?
140デフォルトの名無しさん
2023/07/04(火) 03:12:41.98ID:w64gVUS7141デフォルトの名無しさん
2023/07/04(火) 03:23:48.80ID:H9LDX83D >>140
thxです
thxです
142デフォルトの名無しさん
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 ]
となると予想したら違った
イヤそれをどうやってんのって話
オレが当然こういう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 ]
となると予想したら違った
143デフォルトの名無しさん
2023/07/04(火) 07:51:01.03ID:+0TfLuMN 今のは運良く最初に確保したaの方が返り値として使われたので簡単に領域bが再利用できるけど、もちろん一般には分からない、なのでコンパイラのできることといえば返り値になるかもしれないbの領域はスタック上端に確保しておいてなるべくフラグメンテーションが起きても被害最小に止めるくらいかなと思ったら違った
(ちなみに残す方と捨てる方逆にしても結果は同じ)
確保されたアドレス見るにコンパイラは最初からaが返されるのを見越して[a]の領域だけをmain側のstackに割り当ててるように見える、1回目も2回目も、そして1回目終了時に回収されたbのためのエリアが再利用されてる
レジスタの場合と徹底的にに違うのはメモリの場合「空いてたら使う」を実現するには“未使用域を完全に把握して新しく領域を確保する時未使用域をなるべく活用する”がかなり難しい事
普通は使用してる下端と未使用の上端の境界にsp置いとくくらいが限界
「使用してるのは①〜②、③〜④、....」なんてできるはずない、できるハズないからフラグメンテーション問題が発生する
もちろんこの“できるはずない”事をやってれば実験結果みたいになって不思議ないんだけど流石に無理じゃないかと
(ちなみに残す方と捨てる方逆にしても結果は同じ)
確保されたアドレス見るにコンパイラは最初からaが返されるのを見越して[a]の領域だけをmain側のstackに割り当ててるように見える、1回目も2回目も、そして1回目終了時に回収されたbのためのエリアが再利用されてる
レジスタの場合と徹底的にに違うのはメモリの場合「空いてたら使う」を実現するには“未使用域を完全に把握して新しく領域を確保する時未使用域をなるべく活用する”がかなり難しい事
普通は使用してる下端と未使用の上端の境界にsp置いとくくらいが限界
「使用してるのは①〜②、③〜④、....」なんてできるはずない、できるハズないからフラグメンテーション問題が発生する
もちろんこの“できるはずない”事をやってれば実験結果みたいになって不思議ないんだけど流石に無理じゃないかと
144デフォルトの名無しさん
2023/07/04(火) 07:56:44.61ID:+0TfLuMN でも可能性はあるのかな
プロセス全体で共有されるヒープ全体の使用状況なんか全部管理できないけどローカルの関数に割り当てられたスタックなんてしれてるから使用域、未使用域全部管理してるのかな?
プロセス全体で共有されるヒープ全体の使用状況なんか全部管理できないけどローカルの関数に割り当てられたスタックなんてしれてるから使用域、未使用域全部管理してるのかな?
145デフォルトの名無しさん
2023/07/04(火) 09:22:16.43ID:X5jhzPkA Rustで標準的なチャート生成(グラフ描画)ツールは何ですか?
146デフォルトの名無しさん
2023/07/04(火) 10:49:51.72ID:0JerF0m8147デフォルトの名無しさん
2023/07/04(火) 11:02:31.68ID:XZOUnAWZ148デフォルトの名無しさん
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()スタックフレームに直接アクセスしている
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()スタックフレームに直接アクセスしている
149デフォルトの名無しさん
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が効いている
$ g++ -O3 -fno-inline -std=c++20 -o test test.cpp
$ ./test
foo: 0x7fffb2b5ee40
fff: 0x7fffb2b5ee40
-fno-inlineでインライン展開されていないとするならRVOが効いている
150デフォルトの名無しさん
2023/07/04(火) 13:35:05.06ID:sBWOwVL+ global optimizationがかかったら、どっちにしても、いいようになるのかもしれない
いいようになってくれと思って書くのがいい これはC++もRustも同様
いいようになってくれと思って書くのがいい これはC++もRustも同様
151デフォルトの名無しさん
2023/07/04(火) 15:10:09.42ID:X5jhzPkA Why you shouldn't learn Rust
https://www.youtube.com/watch?v=kOFWIvNowXo
https://www.youtube.com/watch?v=kOFWIvNowXo
152デフォルトの名無しさん
2023/07/04(火) 15:58:19.26ID:Ot1h7oR2153デフォルトの名無しさん
2023/07/04(火) 17:54:17.35ID:fS9gKmDv >>147
せやね
かなぁとは思ってたんやけど時間なくて試せなかった
whichFoo()の条件式を時間のsystem時間の下一桁が奇数か偶数かで決めるように変項したら当たり前みたいにRVOが聞かなくなった
https://ideone.com/T6QJKv
まぁそりゃそうかもね、aかbかどっちが返されるか分からないのに両方とも呼び出し側stackに領域確保してまでRVO効かす事はしないみたいやね
もちろん「フラグメンテーション上等、ローカルスタックに穴開いてもその処理終わるまでなんやから無視、無駄コピーしない事最優先」って実装もできたんやろうけどそうはなってないみたいやな
多分この辺は実装依存なんやろ
せやね
かなぁとは思ってたんやけど時間なくて試せなかった
whichFoo()の条件式を時間のsystem時間の下一桁が奇数か偶数かで決めるように変項したら当たり前みたいにRVOが聞かなくなった
https://ideone.com/T6QJKv
まぁそりゃそうかもね、aかbかどっちが返されるか分からないのに両方とも呼び出し側stackに領域確保してまでRVO効かす事はしないみたいやね
もちろん「フラグメンテーション上等、ローカルスタックに穴開いてもその処理終わるまでなんやから無視、無駄コピーしない事最優先」って実装もできたんやろうけどそうはなってないみたいやな
多分この辺は実装依存なんやろ
154デフォルトの名無しさん
2023/07/04(火) 18:53:05.89ID:axzJnblJ Rustはなんでsimd命令を安定版で使えないんだろうか。
155デフォルトの名無しさん
2023/07/04(火) 19:16:47.25ID:Z5BWnaiC A-simdラベルつきのissues一通り読んでくれば
156デフォルトの名無しさん
2023/07/04(火) 21:51:42.30ID:SetT7x0B157デフォルトの名無しさん
2023/07/05(水) 01:56:15.57ID:jiFtnYl0 多次元配列の実装ってブロードキャストのところが結構ムズいな。今かなり苦労してるわ。とりあえずnumpyのメジャーな機能はすべて実装したものをrust純正で作りたいか。
158デフォルトの名無しさん
2023/07/05(水) 05:24:25.09ID:QQHh6K/1 足りない次元を前から詰めていくだけでは?
それでうまく揃えられなかったらエラー
それでうまく揃えられなかったらエラー
159デフォルトの名無しさん
2023/07/05(水) 08:04:26.42ID:y+DtViwf rustってマスコットも可愛いし
全てが完璧だよな
全てが完璧だよな
160デフォルトの名無しさん
2023/07/05(水) 08:08:30.35ID:BYWVWFg9 golang なんてマスコットがきもすぎて皆逃げだしたもんな
161デフォルトの名無しさん
2023/07/05(水) 10:16:26.75ID:zDnJAWSM あれは何周回っても逆に良いとはならないのがすごい
162デフォルトの名無しさん
2023/07/05(水) 15:06:58.72ID:5Lz4OcyC163デフォルトの名無しさん
2023/07/05(水) 19:58:33.55ID:p4qutnW6 ʕ◔ϖ◔ʔ < 呼んだ?
164デフォルトの名無しさん
2023/07/05(水) 20:49:18.89ID:jiFtnYl0 とりあえず、Rustで多次元配列の実装自体は大分進んできたけど、配列の値は構造体の中にVec型で持つべきかそれとも他の配列構造体においても同じデータをコピーせずに使い回せるようにするために&Vec型で持たせるべきか悩んでるんだよね。&Vec型だとライフタイムの指定とか複数の可変参照について考えなくちゃいけないからだるいんだけど、一つのデータから複数の配列構造体がコピーなしで作れるんだよな。どうするべきか。
■ このスレッドは過去ログ倉庫に格納されています
ニュース
- 「偽サッチャー」「自滅的」「時代遅れ」 高市首相の経済政策を海外メディアが酷評 [蚤の市★]
- 高市首相の答弁書に「台湾有事答えない」と明記 存立危機発言当時 ★2 [蚤の市★]
- 【ド軍】山本由伸、WBC出場を決断!ドジャースが本人の意向を尊重、佐々木朗希はチームが故障歴を懸念で不参加 [鉄チーズ烏★]
- 高市首相の答弁書に「台湾有事答えない」と明記 存立危機発言当時 ★3 [蚤の市★]
- 米大統領報道官「日本と強固な同盟維持、中国とも協力」 [少考さん★]
- JA全農が「新おこめ券」…来年9月末の有効期限を新設、必要経費のみ上乗せ ★2 [蚤の市★]
- 日本人の間で脱税がブーム 所得税の追徴課税額1400億円超、過去最高 [402859164]
- 女の子集合!
- 今年の漢字、きょう発表😉 [583597859]
- 中国人、超ド正論。「チベットやウイグルに住んでるのはチベット族やウイグル族だが、アイヌから奪った土地に住んでる日本人こそ侵略者」 [314039747]
- 百合営業してるアイドル「これは営業だから…んっクチュクチュ」←これ
- 【悲報】メモリだけでなくSSDも値上がりがはじまる♥
