C vs C++ vs Rust Part.3

■ このスレッドは過去ログ倉庫に格納されています
2022/01/27(木) 22:19:47.56ID:avZQ9Wm7
闘え
※前スレ
C++ vs Rust
https://mevius.5ch.net/test/read.cgi/tech/1619219089/
C vs C++ vs Rust Part.2
https://mevius.5ch.net/test/read.cgi/tech/1639539350/
2022/01/27(木) 22:28:23.63ID:4DQKoSsj
前スレまでのあらすじ

---
987 デフォルトの名無しさん sage 2022/01/27(木) 17:14:15.89 ID:hWkHkx2k
>>986
> だから専有機なら上限で用いる
「現在の主流」というのは、「専有機」での話?
それとも一般的な話?

994 デフォルトの名無しさん sage 2022/01/27(木) 17:52:30.63 ID:LojT3k5n
自分が理解できないと相手が敵かつ全て同一人物に見える病気のパターンかねw
皆普通に同じことを指している
何が理解できないのか素直に言ってごらん

995 デフォルトの名無しさん sage 2022/01/27(木) 17:55:13.00 ID:hWkHkx2k
>>994
>>987の質問に答えてから続けてね
2022/01/27(木) 22:58:22.92ID:37eem+FW
あらすじが全くわからんw
2022/01/27(木) 23:13:49.64ID:cK3g3Gve
彼はこの部分がわからなくて連投していたみたいだから横から補足説明しましょう

> 大雑把にI/O観点で二つに分けると
> 昔は同期I/Oでブロックされるからマルチスレッド
> 今は非同期プログラミングでスレッド数はシングルからCPUコア数が上限
> RustのFutureタスク、GoのGoルーチン、JavaScriptの非同期Promiseやコールバック
> いずれもプロセス内スケジューラがI/O多重化(select/epoll)やタイマーなど管理して非同期プログラミングを支えている

昔は自分でOSスレッドを立ち上げて使っていたのに対して
今は自分で立ち上げるのがOSスレッドではなくそれよりも粒度が細かく軽い非同期なタスクで
OSスレッドを立ち上げる役割はそれらタスクの非同期なスケジューリングをするランタイムで
そのスレッド立ち上げ個数は効率最大のためCPUコアスレッド総数と一致するのが望ましいため
GoやRustなどではデフォルト値がそうなっていますよってことですよね
もちろん個数を1個つまりシングルスレッドに設定しても動くところが決定的な違いですね

OSがスケジューリングしてくれるけど重くて大きくリソースを喰うOSスレッドを今は直接使わずに
> RustのFutureタスク、GoのGoルーチン、JavaScriptの非同期Promiseやコールバック
これらのもっと軽くて小さなタスクを使うことで何万も同時に処理できるようになったという話ですね

ちなみにRustでは昔と同じ状況にスレッドとタスクを1:1(=n:n)でも使えますし
シングルスレッドにして1:nでも使えますし上述の状況は汎用的なm:nになりますね
2022/01/27(木) 23:14:48.99ID:fMPJxSq8
明らかに間違ってることを自信ありげに語るのはいつものこと
その人達用の隔離スレだから

>>1
2022/01/27(木) 23:25:15.10ID:iujD8pk9
横から?w
本人だろ、お前w
2022/01/27(木) 23:31:40.86ID:m/Awlcjw
複製おじさんのいつもの自演です
2022/01/27(木) 23:34:30.76ID:iujD8pk9
次はgoogleで2件しかヒットしない、「プロセス内スケジューラ」については説明してくれw
2022/01/27(木) 23:49:40.45ID:cK3g3Gve
非同期タスク群のスケジューリングをするランタイム部分のことではないでしょうか
一般的にプロセスの中に1つだと各スレッドへタスクを割り振るコストが高くて
各スレッドの中に独立して1つずつだと暇なスレッドと多忙なスレッドが偏るデメリットがあり
GoでもRustでもそれらのスケジューリング問題を改善するために
各スレッド内に独立キューを持って効率的に処理しつつもグローバルキューも備えることで解決しているようですね
2022/01/28(金) 00:05:15.17ID:EpTP27or
複製おじはGoでもRustでもまともに非同期プログラミングやったことないんだな
2022/01/28(金) 00:14:38.44ID:9bCXgVDe
>>8
OSスレッドがOSによりスケジューリングされることに対する対比じゃね?
タスクは各プロセスの中にスケジューラーがある
Goは言語ランタイムの中だがRustは各自が好きなのを選んだり自作も可能
2022/01/28(金) 01:31:02.15ID:DUuAybqk
どうせRustは普及しないんじゃないかな、知らんけど。
2022/01/28(金) 08:21:58.40ID:OuJICsLX
rustはc++を食うことがあってもピュアcを食うことはなさそう
2022/01/28(金) 08:42:08.00ID:3x0JFp6u
Cは誤解を恐れずに言えばアセンブリ言語みたいなもんだからな
2022/01/28(金) 13:14:51.22ID:7T77Q24K
>>13
C++ 98以前は Cと似てるし、ライブラリも言語とは無関係に自由に作れたから大丈夫。C++11以後はダメ言語になった。
2022/01/28(金) 13:17:06.82ID:7T77Q24K
そもそも、CやC++が普及したのはTurboCのおかげ。
TurboCはライブラリが分かり易かった。
C++11以後、言語もSTLも両方腐っていてTurboCとは全く違うようになり、
人気もがた落ちになった。
2022/01/28(金) 15:22:28.01ID:ePoQjqFg
何頓珍漢な事言ってんだこのバカ
2022/01/28(金) 18:58:54.57ID:9bCXgVDe
>>8
OSによるプロセスやOSスレッドのスケジューラではなく
プロセスの中で動くタスクのためのスケジューラ
各言語によって言語機能として備えている場合と外部ライブラリによる場合がある

Rustではその中間の形態で言語機能としてはasync/awaitの枠組みのみ提供
さらに標準ライブラリとしてFutureやWakerなどの必要とする定義のみ提供
実際に動作するスケジューラは外部ライブラリで様々な提供がなされ自作もできる
その他はこのスライドの前半の解説など参照

Rustのasync/awaitとスケジューラの話
https://speakerdeck.com/osuke/rust-async-await
2022/01/28(金) 21:04:45.07ID:zI6nuxFY
前スレ946の三つのサイトから数値を入手して和を求める例を練習のためにやってみたんだが
httpのheader取得までとbody取得の2回awaitが入るのがなるほどと思った
あと文字列を数値に変換parseも当然必要だったのでawait部分がちょっと長い
#[async_std::main]
async fn main() -> surf::Result<()> {
let n1 = surf::get("https://httpbin.org/base64/MTEx";); // 111
let n2 = surf::get("https://httpbin.org/base64/MjIy";); // 222
let n3 = surf::get("https://httpbin.org/base64/MzMz";); // 333
let sum = n1.await?.body_string().await?.parse::<i32>()?
+ n2.await?.body_string().await?.parse::<i32>()?
+ n3.await?.body_string().await?.parse::<i32>()?;
assert_eq!(666, sum);
Ok(())
}
利用させてもらったテストサイトはURLで返す値など指定できるhttpbin.org
これで動いて値取得もできて合計値assertも通ったのだが実は落とし穴があることに気付いた
2022/01/28(金) 21:49:38.11ID:YDzvJ7+G
元のJSのコードもだけど
await連発すんなよw
2022/01/28(金) 22:11:12.59ID:zI6nuxFY
そのテストサイトにhttp返答を遅延させるdelay機能があるので試した時に露見した
同時に指定値を返せないようなので数値化と足し算の意味はないが遅延時間を知りたかった
let d1 = surf::get("https://httpbin.org/delay/4";); // 4秒
let d2 = surf::get("https://httpbin.org/delay/5";); // 5秒
let d3 = surf::get("https://httpbin.org/delay/3";); // 3秒
let sum = d1.await?.body_string().await?.parse::<i32>().unwrap_or(0)
+ d2.await?.body_string().await?.parse::<i32>().unwrap_or(0)
+ d3.await?.body_string().await?.parse::<i32>().unwrap_or(0);
結果は12秒かかってしまい期待と異なり直列に実行されていた
肝心なことを忘れていた
2022/01/28(金) 23:09:26.07ID:zI6nuxFY
JavaScriptではPromiseを返す関数を3つ呼べばそれで3つ並行に実行される
しかしRustでは並行に動くタスクを自分で明示的に起動しなければいけなかったのだ
よく考えればGoでも自分で明示的にgoと前置指定することで別goルーチンを起動している
Rustではそれがspawnでありgoと同じくそれによりスケジューラに登録されるようだ
use async_std::task::spawn;
let d1 = spawn(surf::get("https://httpbin.org/delay/4";)); // 4秒
let d2 = spawn(surf::get("https://httpbin.org/delay/5";)); // 5秒
let d3 = spawn(surf::get("https://httpbin.org/delay/3";)); // 3秒
このようにspawnを付けて以降は同じ実行をすると全体で結果5秒となり並行に実行された
言語自体に魔法の仕組みはなく自分で明示的にスケジューラに登録指定するのはわかりやすくて安心した
そのスケジューラも手作りできるらしく興味深い
2022/01/28(金) 23:23:01.28ID:JUEBwV02
自分でblock_on書かないとRustのasyncは身に付かない
2022/01/28(金) 23:49:49.26ID:zI6nuxFY
>>23
use async_std::task::{block_on, spawn};の時
このblock_onを使った
fn main() -> surf::Result<()> {
block_on(async {
let d1 = spawn(surf::get("https://httpbin.org/delay/4";));
// 略
})
}
の簡略版が>>19で書いた
#[async_std::main]
async fn main() -> surf::Result<()> {
let d1 = spawn(surf::get("https://httpbin.org/delay/4";));
// 略
}
ってことか
つまりasync { ... } という非同期ブロックをblock_onによりスケジューラに実行させているわけだ
当たり前だがspawnする以前に最初からスケジューラの掌の上で踊っていたのであった
じゃないと全体を並行に実行できないもんな
2022/01/29(土) 13:58:43.26ID:AW7V4mOd
Rustのasync/awaitはコンパイルされると単純なステートマシンになる
例えばasyncブロック内にfuture1.awaitとfuture2.awaitが順に呼び出されるとする
最初はfuture1をpollする状態でpending中はpendingを返す
そこでreadyになればfuture2をpollする状態へ遷移
そこでもreadyになれば全体としてreadyを返す状態へ遷移
結局スタックレスなコルーチンを実現しているだけなので非常に軽い
26デフォルトの名無しさん
垢版 |
2022/01/29(土) 22:23:27.99ID:12259hRt
起動も切替もスレッドより軽くてリソースも喰わないなら
非同期タスクの欠点は何だ?
2022/01/29(土) 23:26:45.54ID:7irU+4ae
Rustの場合軽量タスクはプリエンプティブじゃないので、変なコーディングすると特定のタスクが実行され続けて、他のタスクがいつまでも実行されないことがある
あとは、OSのスレッドじゃないからスレッドの属性や権限をタスク単位で異なる物にすることはできないとか
2022/01/29(土) 23:44:38.93ID:AW7V4mOd
>>27
前者は間違えて同期ブロッキングする関数を呼んでブロックしてしまった場合
および長時間ずっと全く非同期関数を呼ばずに計算し続けるか暴走する場合
これらはバグならfixで解決
バグでないなら非同期タスク実行スレッドとは別に専用スレッドを用意して実行できるスケジューラもあるのでそれを利用
2022/01/30(日) 00:54:30.09ID:zeCbxF6p
>>28
同じ処理でも入力内容次第で変わるからそんな簡単にいかないよ
2022/01/30(日) 01:16:02.14ID:iWlFH2We
async-stdならgoみたいにタスクが一定時間経っても制御返してくれないときに勝手にスレッド起こしてくれるんじゃなかったっけ
2022/01/30(日) 18:58:15.77ID:9ei8l7Ku
>>29
入出力が行なわれたらタスクのスイッチングが発生するので
意図的にブロッキングするものを呼んでブロックしているケースを除くと
いつまでもタスクが切り替わらないのは演算のみを延々と続けるケースのみになる
対応策としては自分で定期的に手放すか以下の方法で回避

>>30
それは様々な問題点で中止
結局tokioもasync_stdもそのような特殊ケースはspawn()の代わりにspawn_blocking()することで
タスクのスイッチングに影響が出ないように別スレッドで実行させる方針
その場合は結局スレッドを自分で起動して同期ブロッキングで使う昔からの方法と実質同じだが
そのような特殊なケースも含めて統一的にタスクを扱える
2022/01/30(日) 20:50:10.05ID:dly2JmLT
>>31
延々と演算を続ける意図はなかったのに結果としてそうなる場合があるということ
それを最初から想定できてyieldを挟めるなら問題ないのだが現実はそうはなっていない
https://tokio.rs/blog/2020-04-preemption
2022/01/30(日) 21:57:34.87ID:C4ZQL08k
そのbudget方式は戻ってこないとペナルティ与えられないから本質的な解決になっていない
yield相当をコンパイラが上手く挟むのも無理だからプログラマーが自らすればいい
2022/01/30(日) 22:13:02.89ID:46YJeJfB
歴史から学ばない人
2022/01/31(月) 00:52:40.26ID:wgfsi16C
歴史ってのがマルチタスクOSにおける cooperative vs preemptive の話だとするなら前提が違いすぎない?
任意のアプリケーションが動く可能性のあるOSと自身のプログラムのルーチンが実行されるアプリとではスケジューラに求められる制約条件は違って然るべきかと
2022/01/31(月) 07:30:22.38ID:qlFEomu1
シングルスレッドかつcooperativeなJavaScriptがNode.jsを含めて成功しているように問題なく動作する
同期ブロッキングを完全排除してしまえばタスク切り替えが起きない場合すなわちスケジューラに戻らない場合は
入出力もなくループでメモリ上演算を繰り返している場合となる
そのような場合でもループ内で自分で定期的にスケジューラへ制御を戻してやればよい
そのためにRustではtask::yield_now()がある
2022/01/31(月) 10:15:42.24ID:O/M0tTc6
イベントループがハングアップした時の対処とか考えたことないでしょ?
バグだからfixすればいいじゃ能がない
歴史に学ぶといいよ
2022/01/31(月) 10:24:59.23ID:qlFEomu1
>>37
GoやJavaScriptなどは言語内蔵だが問題が起きたことはない
Rustは言語から切り離されているが同様
いずれもそれらのランタイムの製作者以外がその部分のコードを触ることはなくバグが発生しようがない
2022/01/31(月) 10:27:00.53ID:/Hwves02
>>37
> イベントループがハングアップした時の対処とか考えたことないでしょ?
そういうのは上位で対処(タイムアウトでプロセス再起動とか)することもあるからケースバイケースでしかない
2022/01/31(月) 10:46:15.49ID:5QuAHu0k
イベントループがハングアップするってなに?
epoll_waitをブロッキングで使う場合でもタイムアウト設定すればハングアップなんてしないでしょ?
2022/01/31(月) 11:35:28.84ID:BNlzdeLS
タイムアウト設定忘れみたいなバグの話でしょ
そんなに引っ張る話でもないと思う
2022/01/31(月) 12:52:34.54ID:qlFEomu1
>>41
それは万が一あるとしても非同期スケジューラランタイム実装者の管轄
その利用者である一般プログラマーが引き起こすバグではないため関係ない
2022/01/31(月) 13:02:21.71ID:dnOSCP4X
> epoll_waitをブロッキングで使う場合でもタイムアウト設定すればハングアップなんてしないでしょ?
の話だぞ?
2022/01/31(月) 13:17:00.70ID:wgfsi16C
歴史って言葉持ち出してるんだしそんなしょうもない話じゃなくてもっと有名なエピソードなんじゃないの
2022/01/31(月) 13:24:49.76ID:HGCqFbKg
「Rustのタスクの欠点は?」

「プリエンプティブじゃないところ」

「プログラマーが注意すればいいだけだから問題ない」
2022/01/31(月) 13:35:30.64ID:qlFEomu1
そりゃepollやselect相当を自分で使っていてミスをすればイベントループがハングアップするのは当たり前
しかし非同期タスクのイベントループは非同期スケジューラの中にあって自分でepollやselectを扱わなくてよくなった
だから自分のミスでイベントループがハングアップすることはなくなった
それ以前の歴史では色々あったが状況が変わった
2022/01/31(月) 13:51:12.77ID:dnOSCP4X
はいはい、理想的な環境で羨ましいですね

これでいいかな?w
2022/01/31(月) 14:52:14.32ID:QZLkrRiL
メモリ開放忘れみたいなバグの話でしょ
そんなに引っ張る話でもないと思う
2022/01/31(月) 14:57:56.44ID:wgfsi16C
バグ耐性高めるためにタスクをプリエンプティブにしたいならすれば良いのでは
プログラマーのスタンスに合わせて適切な物を選べるよう選択肢は用意されてると思うが
何について言い争ってるのかよくわからない
2022/01/31(月) 15:37:58.82ID:UHXhumHV
GoやNode.jsが上手くいってる主因は非同期並行プログラミングのしやすさ
両者は方向性が真逆だけどそこが共通点で時代の要請
逆にC++が振るわないのは非同期並行プログラミングのしにくさ
その状況でRustが登場して非同期並行プログラミングのしやすい環境も装備されたという流れ
2022/01/31(月) 18:03:53.30ID:dLpRlGxR
>>49
タスクはプリエンプティブにはできないよ
スレッド使えって言ってるの?
2022/01/31(月) 18:33:21.76ID:fs07R1AL
>>51
そうじゃね?
2022/01/31(月) 18:56:25.41ID:UHXhumHV
Node.jsもプリエンプティブではないけど困っていない
タスクがプリエンプティブではないことを欠点だと主張する人は
欠点だという具体的な例を挙げてから主張すべき
2022/01/31(月) 19:02:29.41ID:wgfsi16C
>>51
そうだよ
ここで言うタスクはtokioやasync-stdの用語のタスクのことね
spawn_blockingでタスク生成すれば専用スレッドが割り当てられるので特定のタスク選んでプリエンプティブにできる (OSにタスクスイッチの制御を委譲できる)
2022/01/31(月) 19:03:44.27ID:wgfsi16C
>>53
spawn_blockingみたいなAPIが用意されてるのはプリエンプティブなタスクの需要はあるんじゃないの
そこを否定する意味が分からない
2022/01/31(月) 19:28:57.02ID:UHXhumHV
>>54
それはレイヤーが違う
まずOSスレッドは何をしようが常にプリエンプティブ
だからOSスレッド上で動いているタスクも途中で一時中断されたり再開されたりしうる
だからといってタスクをプリエンプティブだと言わないのはタスクの階層で話をしているため
タスクスケジューラに制御を戻さない限り他のタスクに切り替わらないため「プリエンプティブではない」と言われる

次にspawn_blockingで起動されるのもタスクの一つ
だからタスクスケジューラが管理するスレッドの中で動く
それがspawnだと一つのスレッドに対するキューに複数のタスクが割り当てられる
一方でspawn_blockingは一つのスレッドに対するキューにそのタスクのみが割り当てられる
それだけの違いであり両者ともにタスクスケジューラの管理下にあるタスクである
もちろん動作中も終了後もタスクとして同じ扱い

つまりspawn_blockingはプリエンプティブにするものではない
同じタスクでありスタックレスなコルーチンであることも同じ

>>55
その意見は成立していない
2022/01/31(月) 19:56:01.95ID:wgfsi16C
>>56
確かにプリエンプティブという用語を使ったのは適切ではなかったかもね
タスクがスレッドを占有するか他タスクと共有するか選択できると言い換えればよいかな
2022/01/31(月) 21:28:17.75ID:L8OnQfxN
>>54
最初からspawn_blockingで呼ぶべきだと誰もがわかるような処理だけじゃないよ

Goと同じでRustもそのうちプリエンプティブなスケジューラが作れるような機構を用意すると思うけどね
2022/01/31(月) 22:11:33.46ID:qlFEomu1
>>58
preemptiveは必要ない
シングルスレッドかつcooperativeなJavaScriptがNode.jsを含めて問題なく動作し成功している
2022/01/31(月) 22:59:08.63ID:UPbAych9
Rustってデータ競合を許さない、ってあたりが並列処理とめちゃくちゃ相性良いように見えるけど、
まだそういうスケジューラとか非同期ランタイムらへんの仕組みがちっとも枯れてないんだね
将来のデファクトがどうなりそうか、ってのはある程度見えてきてるのかな?
2022/01/31(月) 23:10:00.73ID:9ggC0jgK
>>59
tokioの開発者もasync-stdの開発者もそうは考えてない

a major source of bugs and performance issues in concurrent programs: accidental blocking.
https://async.rs/blog/stop-worrying-about-blocking-the-new-async-std-runtime/
2022/01/31(月) 23:16:15.33ID:UHXhumHV
>>61
それはすぐに廃止された
そして現在tokioもasync-stdもプリエンプティブを許すコードは存在しない
■ このスレッドは過去ログ倉庫に格納されています
5ちゃんねるの広告が気に入らない場合は、こちらをクリックしてください。