結局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/01(土) 16:51:31.78ID:DYHMlldq
そもそも、STLなんて後から入ったものだから、それを使うことは
強制されて無いのに、STLの欠点がC++の言語としての欠点に
勝手にみなされてっしまってる。
これまでずっと、C++はBetter Cとして使うのが好まれていたのに、
Better Cとして使うのは時代遅れなんていっておきながら、
STLならではの欠点を指摘して、だから、C++を使うのは馬鹿なんだ、
と言ってくる。
Better CとしてSTLではないライブラリを使っている限り、
その欠点は入ってこないにもかかわらず。
2023/07/01(土) 16:58:20.28ID:DYHMlldq
そもそも、C++でもsmart pointerを使うのが必須、などと言っているが、
むしろ、そうすることが煩わしさを生む。
そうすることによって、むしろめんどくさくなったり、分かりにくくなったり
することがあるのだ。
勝手に smart pointer を使うのが必須であるとして、それがとても使いにくい
ことがC++の欠点だと昇華され、だから、Rustでないとダメなんだと。
しかし、smart pointer を大々的に使おうとしたことが煩わしさの根本で
あることに気づいてない。
また、速度比較などでも、伝統的な Better C としての C++ ならば、
STL も smart pointer も使ってなかったから、Rustより速いにもかかわらず、
勝手に「最新のC++のやり方」とその人が思っていものを使って、
遅い結果にしてしまって、それをC++の「最高の結果」だと勝手に断定
してしまう。
世の中には「改悪」という現象がある事をその人は知らない。
2023/07/01(土) 17:04:31.83ID:HHzlehAK
C#でWindowsフォームアプリで作成中にWin10では別途.net coreを配る面倒な事がわかってショックだわ。
Windowsフォームアプリケーション (.NET Framework)にしたいのだが作り直ししか無い?
2023/07/01(土) 17:07:45.38ID:HHzlehAK
>>39
すまん誤爆しました。m(..)m
2023/07/01(土) 17:31:35.89ID:DYHMlldq
マイクロソフトのMFCも欠点が指摘されやすいが、しかし、STLより便利なところもある。
MFCにはなかった問題点をSTLは沢山入れてしまった。
MFCはSTLと距離を置いている。
そして、デファクトスタンダードとしては、現代においてC++を使うとは
MFCを使うことに他ならない。ということは、STLはほとんど使われていない。
にも関わらず、教科書に書いて有るようなSTLの非常に煩わしいやり方を持ってして
C++が使いにくい、と言ってくる。
C++が愛されてきたのは、MFCのせいであってSTLのせいではないことを知るべきである。
2023/07/01(土) 18:38:56.14ID:PswK3kno
プログラマなのによくこんな駄文書けるな
ChatGPTで水増しさせた?
2023/07/01(土) 18:41:37.69ID:Ij0EQZmJ
偏見を承知で言うと
マイクロソフト依存症の人はそんな人が多い
2023/07/01(土) 19:01:28.94ID:kqCQiy8S
C#使いなよ
2023/07/01(土) 19:03:32.57ID:DYHMlldq
C#は絶対嫌だ。
2023/07/01(土) 19:05:40.48ID:ulppHLtb
>>14
Zig使ったこと無いけどエコシステムが整ってるなら使ってみたいな
2023/07/01(土) 19:08:35.56ID:DYHMlldq
そもそもSTLがC++のメインライブラリなどと思っている人は、
C++をまともに使ったことが無い人だ。
2023/07/01(土) 19:17:18.65ID:ulppHLtb
boostだろメインライブラリは
2023/07/01(土) 19:18:04.10ID:X54p6y1Z
C++のテンプレートは理想(言語仕様)と現実(コンパイラ実装)が乖離してたからな
module導入で多少はマシになるかもしれないけどnamespace残したままだからカオス度が増してしまった
2023/07/01(土) 19:23:55.22ID:DYHMlldq
MFCは問題が有ったが、それは主にWin32のWindow関連の
APIとの連携においての話。
STLはそれ以前の問題で書き方がおかしい。
現実にプログラミング経験が少ない人が勝手に考えた感じだ。
2023/07/01(土) 20:02:19.26ID:l24kSvWl
>>36
ライブラリがな
2023/07/01(土) 20:20:46.38ID:uVimT7AM
>>36
HaskellがCやRustより速いとマジで言ってるの?
53デフォルトの名無しさん
垢版 |
2023/07/01(土) 21:49:33.99ID:jAprZXCn
>>39
>昇華
doubt
54デフォルトの名無しさん
垢版 |
2023/07/01(土) 22:14:34.22ID:jAprZXCn
>>38
全体は同意
2023/07/02(日) 01:00:29.09ID:jzpBLYtw
C#に慣れるとMFCでGUIは二度と書きたくない
56デフォルトの名無しさん
垢版 |
2023/07/02(日) 01:02:51.98ID:ieCgx+H2
行列のサイズがでかくなるとシングルスレッドよりもrayonによる並列演算の方が速くなってくるな。
2023/07/02(日) 06:54:41.92ID:jdLDLriS
そもそもMFCはfull freeにならんから
ちょっと前まではそうだった、今もそうだよな

自分用なら、コンソール+C#(OSに同梱の古い枯れたやつ)でいいし
2023/07/02(日) 09:53:50.16ID:UHuzldRb
>>55
開放するの忘れてリークしまくる。
2023/07/02(日) 13:18:42.16ID:Xyim1/0J
GUIがかんたんになったのはここ10年くらいでしょ
それ以前は何を選んでも面倒で難しかった
2023/07/02(日) 13:20:54.90ID:Xyim1/0J
IDEの性能や極まったポトペタ感で昔より作りやすくなってる
言語そのものじゃなくその周辺が作りやすさを決めてるだけじゃないか?
2023/07/02(日) 13:53:16.00ID:Xyim1/0J
RustのGUIってそんなに作りやすいか?
2023/07/02(日) 14:19:07.95ID:fG7MSDtE
GUI用のDSL(マークアップ)がある言語に比べればまだまだでしょ
Rustもマクロ使えばDSLくらいは組めそうだけど
最近は内部ブラウザみたいな代替技術があるから需要が少なそう
63デフォルトの名無しさん
垢版 |
2023/07/02(日) 15:12:58.96ID:BV1CApnr
>>59 難しいと思ったことは一度も無いな
>>61 Tauri は糞
2023/07/02(日) 15:49:02.90ID:OihXi6HP
TauriやElectronはGUIといっても
・マルチプラットフォームでスマホ対応も進行中
・Webと同じ知識技術で作れる
・Webアプリへとの共有や移行が容易
と大きなメリットがあり
VSCodeなどこの方式で成功しているアプリが多数ある
2023/07/02(日) 15:52:44.86ID:nM9kee/9
Rustで全てが非同期かつコンポーネントベースのGUIライブラリを作る機運が生まれてる
描画から全部自前でやる本当のネイティブ版Reactのようなもの
2023/07/02(日) 18:19:31.18ID:owOvhd2B
GUIをC++やRustでやる意味とは
UEだけだろ意味あるの
2023/07/02(日) 18:40:53.06ID:nM9kee/9
>>61
全てがasyncでコンポーネント化されている
これこそ次世代のGUIライブラリとなる
これを作れるのはrustのみ
2023/07/02(日) 19:45:01.65ID:l4k0OEWm
すべてasyncとか死ぬほどめんどくさそう
2023/07/02(日) 19:49:14.43ID:T91CalxQ
いやGUIだとそれが普通なんだよ
UIスレッドでブロッキングAPI使えないのだからその設計が普通
今までのライブラリの設計がミスってた
2023/07/02(日) 19:55:13.31ID:T91CalxQ
このライブラリがかなり理想に近い
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スレッドへの同期コンテキストが必要でその分さらに遅い
2023/07/02(日) 21:59:34.59ID:jzpBLYtw
結局のところOSの描画API呼び出すから、
最終的にUIスレッドに描画任せないといけないのはどのライブラリでも変わらん
2023/07/02(日) 22:04:50.40ID:OihXi6HP
>>74
それは違うんじゃないか
例えばファイル読み書きもOS呼び出すけど
並行プログラミングができていれば
シングルスレッドでもファイル読み書きでブロックされず
待ち時間に他の作業をできるよ
そこにマルチスレッドや描画専用スレッドの必要性はない
2023/07/02(日) 22:08:24.86ID:jzpBLYtw
OSの仕組み理解してなさそう
2023/07/02(日) 22:27:06.93ID:ajw2MJiV
>>76
OSの仕組みを理解していないのは君だ
Linuxならepollなど
WindowsならIOCPなどを勉強しなさい
2023/07/02(日) 22:31:37.38ID:T91CalxQ
なぜ非同期が良いか?
UIスレッドが呼び出しても何の問題もないことが保証されてるから
これはnode.jsが気が付かせてくれたパラダイムシフトだよ
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の証明になっている
■ このスレッドは過去ログ倉庫に格納されています