Conversation
| ### step2 | ||
|
|
||
| * https://github.com/colorbox/leetcode/pull/7/files | ||
| * `+=` しながら `checker` 文字列を作っている。Python だと文字列コピーが発生する (ただし最適化の実装による) のでやりたくない感じなんだが、C++ だとどうなの? |
| * https://github.com/colorbox/leetcode/pull/7/files | ||
| * `+=` しながら `checker` 文字列を作っている。Python だと文字列コピーが発生する (ただし最適化の実装による) のでやりたくない感じなんだが、C++ だとどうなの? | ||
| * 上記だと `checker` を reverse している。 | ||
| * step1 でのコメントも踏まえて `step2/Solution` はとりあえず書いてみた。関数では文字列コピーして返しているから冗長な感じはする。できればコピーはせずに使いたい気もする (できるのかな?無理そう) |
There was a problem hiding this comment.
まず、これは named return value optimization が働いてコピーが自動的に消える可能性があります。
コピーしたくなければ、リファレンスかポインターを引数に渡します。
There was a problem hiding this comment.
std::string to_lower_alnum(std::string& s) {
...
}
bool isPalindrome(string s) {
std::string lower_alnum_string = to_lower_alnum(s);
...のような感じでしょうかね。(N)RVO の話はあまり考慮していませんでした。
https://cpprefjp.github.io/lang/cpp17/guaranteed_copy_elision.html
ちょっとフォローしきれていないので別途勉強します。
もともとここで意図していたのは、isPalindrome の引数 s の非連続 (である可能性がある) な部分文字列がlower_alnum_string であり、lower_alnum_string の中の文字 (char) はすべて s に含まれているはずなので、s のメモリを再利用して lower_alnum_string を (string_view のようなイメージで) 作れないのか、というものでした。
非連続な部分文字列なのでやはり難しい気がしました。
There was a problem hiding this comment.
void to_lower_alnum(std::string& from, std::string* to);
std::string lower_alnum_string;
to_lower_alnum(s, &lower_alnum_string);こうです。
長い文字列から数文字を消去した文字列を空間をできるだけ再利用して表現したいということですね。
Rope というデータ構造を思い出します。これは、木構造で文字列を管理して編集されていない部分木を共有します。split, concatenate が O(log n) でできます。
Piece Table はテキストエディターなどで使われるもので、変更履歴を追いかけていくものです。
一般的に連続したメモリーに対しての処理は速いので普通はコピーしたほうがいいでしょうが、テキストの編集は頻繁に行われてきたのでよく研究されています。
There was a problem hiding this comment.
概ね連続したメモリーのコピーは 1 clock で4-8バイトくらいできるのではないかと思います。
木構造をたどるとどうしてもたどるたびに数クロックかかるでしょう。
結局、切り替えることによってどれくらい実際に速くなるかを見積もって、別のライブラリーを使う必要がでてくるデメリットなどと勘案するという比較衡量をして欲しいですね。
There was a problem hiding this comment.
ありがとうございます!
こちらでもコメントいただいている箇所ですね。先に書き込み先を用意して関数にそこに書き込んでもらう、というのが馴染みないですが、慣れていきたいと思います。
string を引数に値渡しで渡すと、コピーが発生します。stringをコピーする場合、元の文字列が格納できるサイズのメモリをヒープに確保し、文字列をコピーします。できればこのストは避けたいです。 const string& と const 参照渡ししたほうが良いと思います。
また、戻り値を string の値型で返すと、同じようにコピーが発生することが多いと思います。引数に参照型の引数を追加し、そこに出力を渡すのが良いと思います。
サイズが十分小さかったり、定数倍の速度が求められないのであれば、値渡しをしたり、値型を返しても良いかもしれません。
There was a problem hiding this comment.
概ね連続したメモリーのコピーは 1 clock で4-8バイトくらいできるのではないかと思います。
木構造をたどるとどうしてもたどるたびに数クロックかかるでしょう。
なるほど…このあたりの数字感覚がなかったのですが、どのようなロジックで推論されるものでしょうか。
追記: 後のコメントから辿って多少理解しました。32 or 64 ビット CPU の想定から 4-8 バイトと見ているんですね (ただのコピーなので機械語的にも 1 命令で、特にオーバーヘッドがない処理のはずなので)。
Ryotaro25/leetcode_first60#66 (comment)
結局、切り替えることによってどれくらい実際に速くなるかを見積もって、別のライブラリーを使う必要がでてくるデメリットなどと勘案するという比較衡量をして欲しいですね。
今回であれば 1 <= s.length <= 2 * 10^5 という制約で、ASCII で 1 char / 1 byte と仮定するなら、200KB くらいなので、丸ごとコピーすることになるとすると 4 bytes / clock と仮定して 50K clocks くらい必要。昨今の CPU なら数 GHz / sec くらいなので、1/100 秒 (10ms)とかそのくらいでしょうか。まあこのくらいならいいかな…という気がしてきます。
There was a problem hiding this comment.
先に書き込み先を用意して関数にそこに書き込んでもらう
Google のスタイルガイドもともとこうだったんですが、最近はしないようになっているみたいです。NRVO が効く前提なんでしょうか。
There was a problem hiding this comment.
数字感覚
実行時間の見積もりは、具体的な CPU によって異なる+上から抑えたいためかなり適当です。
- 2000年頃のアーキテクチャでも浮動小数点 double は64ビットなので64ビットは一命令で動かせるでしょう。これは遅い見積もりです。
- 変更なしでメモリーをコピーするならば SIMD 命令などではるかに速いでしょう。
- サイズによっては L1/L2 キャッシュが効く。ないとメモリー帯域は 10 GB/s の桁。
Jeff Dean の表(2012年)は Read 1 MB sequentially from memory 250 us としていました。
なんていうか、見積もりは色々な傍証から「こんなものだよな」とやっているのでそんなにロジカルなものではないです。機械語でどうなりそうかを考えてアーキテクチャの知識と照らし合わせています。
| * https://qiita.com/LdCqh1/items/92f286ceb0ab96dc3c09 | ||
| * https://zenn.dev/reputeless/books/standard-cpp-for-competitive-programming/viewer/string | ||
| * > C++ 標準ライブラリは、任意の文字型 Char に対して、便利な文字列処理を提供するためのクラステンプレート std::basic_string<Char> を定義しています。それを char 型に対して特殊化(std::basic_string<char>)したものが std::string です。 | ||
| * `Char` と `char` って C++ だと違うものな?(TODO) |
There was a problem hiding this comment.
ここでの Char は一般的に Char と書けば通じるものではありません。
template <class Char> で表現するように、wchar_t などを代入するという意味(変数 x みたいな意味)です。
| * `Char` と `char` って C++ だと違うものな?(TODO) | ||
| * 軽く検索してみたがよくわからん | ||
| * `union` (TODO) とかあるんだ。。 | ||
| * `size_t` という型がある |
There was a problem hiding this comment.
sizeof の返す型。vector size もこの型のことが多いです。
「符号なし」なので引き算をするとたまにあふれてはまります。
| @@ -0,0 +1,27 @@ | |||
| class Solution { | |||
| private: | |||
| std::string to_lower_alnum(std::string s) { | |||
There was a problem hiding this comment.
| std::string to_lower_alnum(std::string s) { | |
| std::string to_lower_alnum(const std::string& s) { |
それかmodern C++ ではstring_viewを渡すことが多いかと思います。
There was a problem hiding this comment.
あ、なるほど、string_view はこういうので使えるんですね。
| class Solution { | ||
| private: | ||
| std::string to_lower_alnum(std::string s) { | ||
| std::string lower_alnum_string; |
There was a problem hiding this comment.
もしsのサイズが大きれば、ここは先にcapacityをreserveするのも良いかと思います。
There was a problem hiding this comment.
reserve の時間見積もりはこのあたりにあります。一行を加える管理コストに足るメリットがあるかという良し悪しを評価するという方向に頭を動かしたかということが大事です。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.isflp7vsmzk2
There was a problem hiding this comment.
reserve をそもそも知らなかったのですがこれですね。
https://cpprefjp.github.io/reference/string/basic_string/reserve.html
「計算速度の見積もり」の項は何度か見ているものの、すべて身についている感じはしないので折に触れて確認していきたいと思います。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.xbcr3241jgv8
| @@ -0,0 +1,27 @@ | |||
| class Solution { | |||
| private: | |||
There was a problem hiding this comment.
Google guideではpublicを先に宣言することがお勧めされております!
There was a problem hiding this comment.
一応、プロダクションでは分割コンパイルをするというのを意識しておきましょう。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.cxz8lxsufnbn
There was a problem hiding this comment.
ありがとうございます。このあたりを確認しました。分割コンパイルについての意識はまだ低いのでもう少し C++ に慣れたら、どういうヘッダファイルが想定されるか考えてみようと思います (production のコードでちょくちょく読んでいるので読む方の認識はあるのですが、書くほうがまだ今ひとつ)。
元の質問に戻ると、class 定義は .h に書かれてどういうクラスであるだけが書かれているので、外からのインターフェースが前で(サイズを決めるのに必要な) private 情報は後ろに来ます。ただ、普通は実体がさらに後ろに回っているので、半分は肯定ですね。
|
|
||
| class Solution { | ||
| private: | ||
| string to_lower_alnum(string s) { |
There was a problem hiding this comment.
string を引数に値渡しで渡すと、コピーが発生します。stringをコピーする場合、元の文字列が格納できるサイズのメモリをヒープに確保し、文字列をコピーします。できればこのストは避けたいです。 const string& と const 参照渡ししたほうが良いと思います。
また、戻り値を string の値型で返すと、同じようにコピーが発生することが多いと思います。引数に参照型の引数を追加し、そこに出力を渡すのが良いと思います。
サイズが十分小さかったり、定数倍の速度が求められないのであれば、値渡しをしたり、値型を返しても良いかもしれません。
There was a problem hiding this comment.
ありがとうございます。こちらの oda さんのコメントと合わせて確認しました。
https://github.com/ryosuketc/leetcode_grind75/pull/5/files#r2274872702
125. Valid Palindrome
https://leetcode.com/problems/valid-palindrome/