汎用クラスライブラリ - バグ、説明、質問、使用上の特徴、提案 - ページ 19

 
ワシリー・ソコロフ
は、結局のところ、そのクラスのための独自の特殊化関数を書くことができます。
 
コンビナート です。
結局のところ、あるクラスの関数を自分で特殊化したものを書けばいいのです。

インターフェイスがなければできないことです。

 
ワシリー・ソコロフ

インターフェイスがなければできないことです。

インターフェイスがないと、何ができないんですか?)
 
コンビナート です。
インターフェイスがないとできない?)

まあ、考えてみてください。とにかく、スレッドを乱立させないで下さいね。

 
ワシリー・ソコロフ

問題点

当然ながら、MQL5ユーザー環境では、GetHashCodeは正しく実装できません。これは、すべてのオブジェクトにアクセスできるわけではないからです。また、double intなどのプリミティブなメンバでは問題ない実装でも、複雑なオブジェクト(クラス、構造体、さらには列挙体)では、名前によってハッシュが計算されることになります。もし、何千もの CObject や ENUM_something_that があれば、CHashMap や CHashSet は LinkedList に堕落してしまうでしょう。

ユーザーレベルではオブジェクトの名前しか分からないので、これを避けることはできません。

したがって、複合 型のすべてのオブジェクトのハッシュは互いに等しく、ここでの検索コードは完全な配列の列挙を含んでいます。

実際、これは非常に重要なことで、ルートでGenericを使用することの全ポイントを上書きしてしまうほどです。 ポイントは、ほとんどの場合、列挙体、構造体、クラスといった複雑なオブジェクトを関連付けたり保存したりする必要があることです。単純な型での操作が必要なら、もっとシンプルなものでもいいはずです。

Genericコレクションがクラスオブジェクトと正しく動作するためには、これらのクラスはIEqualityComparableインターフェースを実装する必要があり、EqualsとHashCodeのメソッドが定義されています。つまり、ハッシュコードの計算方法をユーザーが自分で設定しなければならないのだが、.NetではMQL5などで行われているような自動実装は不可能なので、今のところこれしか選択肢がないのである。

 
ロマン・コノペルコ

Genericコレクションがクラスオブジェクトと正しく動作するためには、これらのクラスはIEqualityComparableインターフェースを実装する必要があり、その中でEqualsとHashCodeのメソッドが定義されています。つまり、ハッシュコードの計算方法をユーザーが設定しなければならないのだが、.Netで行われていたように、例えばMQL5を使って自動的に実装することは不可能なので、今のところこれしか選択肢がないのだ。

ローマン、忘れてましたけど MQL5にはインターフェースがありません.今回は、MQL5におけるインターフェースの概念について紹介します。

p.s. しかし、仮にMQL5でインターフェースが登場したとしても、構造体や列挙型の問題は未解決のままでしょう。

 
ワシリー・ソコロフ

ローマン、忘れてましたけど MQL5にはインターフェースがありません.今日のMQL5でインターフェイスの話をするのは、悪意のある当てこすりデマゴギー です。

p.s. しかし、仮にMQL5でインターフェースが登場したとしても、構造体と列挙型の問題は未解決のままだったでしょう。

MQL5では、データ転送の特殊性から、クラス、構造体、列挙体に対して同時に動作するテンプレートメソッドを書くことは現時点では不可能です。
 
ロマン・コノペルコ
今のところMQL5では、データ転送の特殊性から、クラス、構造体、列挙体に対して同時に動作するテンプレートメソッドを書くことが原理的にできません。

そういうことなんです。しかし、MQL5環境は、そのオブジェクトについてすべてを知っているのです。オブジェクトへのポインタを持ち、列挙型の識別子をすべて知っている(EnumToString)。そのため、システムとして、また雑食性の高い関数としてGetHashCodeが必要なのです。

また、最終的に複数のインターフェース継承を可能にします。Genericを通常のインターフェイス用に書き換えれば、スイートスポットができあがります。

 

MQの開発者は、C++の多重継承で散々痛い目にあったので、今では多重継承の発現を恐れている、というのが実情だ。その結果、あるくだらないもの(多重継承)を、別のくだらないもの(とんでもない継承チェーン)を使って回避することが提案されています。

インターフェースは継承とは 関係ないことを理解する必要があります。インターフェースとは、あるクラスが与えられた機能を提供するためにコミットする宣言のことです。2つのクラスが同じ機能を実装している場合、それらは互いに継承されるべきではない。継承すること=悪

 

トレーディング、自動売買システム、トレーディング戦略のテストに関するフォーラム

汎用クラスライブラリ - バグ、説明、質問、使用上の特殊性、提案

ローマン・コノペルコ さん 2017.12.18 16:29

1) CHashMapの新しいボリュームを計算するためにCPrimeGenerator::ExpandPrimeメソッドが使用されるボリューム成長係数(容量)が1.2に等しくない。

int CPrimeGenerator::ExpandPrime(const int old_size)
  {
   int new_size=2*old_size;
   if((uint)new_size>INT_MAX && INT_MAX>old_size)
      return INT_MAX;
   else
      return GetPrime(new_size);
  }

この方法では、古いコレクションのサイズを2倍し、その結果得られた値について、上位の素数から最も近いものを見つけ、それを新しいボリュームとして返します。

容量の初期値について - 確かに、非常に小さいですね。

しかし一方で、コンストラクタは常に存在し、初期容量を明示的に指定することができる。

class CHashMap: public IMap<TKey,TValue>
  {
public:
                     CHashMap(const int capacity);
                     CHashMap(const int capacity,IEqualityComparer<TKey>*comparer);
  }

だから、ここで何かを変える意味はあまりないと思っています。


そうです、私が間違っていたのです、悔やんでいます。
CHashMapの体積増加率(容量)は、本当に2以上です。
間違いのご指摘ありがとうございます、無駄な時間を過ごしてしまい申し訳ありませんでした。



一方、CPrimeGeneratorの実装については、なんとか時間をとって勉強することができました。

//+------------------------------------------------------------------+
//| Fast generator of parime value.                                  |
//+------------------------------------------------------------------+
int CPrimeGenerator::GetPrime(const int min)
  {
//--- a typical resize algorithm would pick the smallest prime number in this array
//--- that is larger than twice the previous capacity. 
//--- get next prime value from table
   for(int i=0; i<ArraySize(s_primes); i++)
     {
      int prime=s_primes[i];
      if(prime>=min)
         return(prime);
     }
//--- outside of our predefined table
   for(int i=(min|1); i<INT_MAX;i+=2)
     {
      if(IsPrime(i) && ((i-1)%s_hash_prime!=0))
         return(i);
     }
   return(min);
  }


そして、主にパフォーマンスを向上させるための提案がいくつかあります。


1.あいまいな行動をなくす。
CPrimeGenerator::ExpandPrime にパラメータとして "INT_MAX - 10" を渡すと、結果 "INT_MAX" が返されます。
CPrimeGenerator::GetPrime にパラメータとして "INT_MAX - 10" を渡すと、同じ結果が返されます: "INT_MAX - 10".

また、いずれの場合も、返される値は素数ではないため、ユーザーに誤解を与えることになります。



2.7199369より 大きい数に対してGetPrimeを 呼び出すと、メモリの節約が優先されますが、これは相対的に低いパフォーマンスと無駄な計算を正当化するものではありません。

提案します。
- 配列CPrimeGenerator::s_primes[] の最後の値と数値の比較を追加し、72個の配列要素すべてに対して不必要な列挙を行わないようにしました。
- 単純な数の動的探索 (行の中のすべての数を通過) をCPrimeGenerator::s_primes[] のような定義済みの値の配列で置き換えますが、増分は二次ではなく線形になります。
値の増分は約100万になる(配列の最後の要素のs_primesの増分に近い数字)。
要素数は最大 3000、値は 8M から INT_MAX までとする。
配列は上界バイナリサーチで探索され、必要な反復回数は12回である。


3.7199369 より小さい数に対してGetPrime を呼び出すと、最悪の場合、配列CPrimeGenerator::s_primes[] の 72 個の値のすべてを線形探索することになります。

という提案をしています。
- 配列の要素数を70にする。(を削除することで、最初の2つ、または最初と最後の1つを削除します)。

const static int  CPrimeGenerator::s_primes[]=
  {
   3,7,11,17,23,29,37,47,59,71,89,107,131,163,197,239,293,353,431,521,631,761,919,
   1103,1327,1597,1931,2333,2801,3371,4049,4861,5839,7013,8419,10103,12143,14591,
   17519,21023,25229,30293,36353,43627,52361,62851,75431,90523,108631,130363,156437,
   187751,225307,270371,324449,389357,467237,560689,672827,807403,968897,1162687,1395263,
   1674319,2009191,2411033,2893249,3471899,4166287,4999559,5999471,7199369
  };

- 入力値が新しい配列CPrimeGenerator::s_primes の6番目の値以下であれば - 直線的に数字を反復する(最大6回まで比較)。
- それ以外の場合は、配列の7番目と70番目の値の間の上界バイナリサーチを使用します(約6回繰り返し)。

バイナリサーチと比較して性能の低下がない限り、リニアサーチのみを使用するという考え方です。
要素数の目安である6個は例として挙げたもので、実際には上界型2値探索の具体的な実装に依存する。
特定の機能の呼び出し強度が低いことによる全体的な性能向上は、この機能を向上させるための作業を行うほど有益ではない場合があります。