覚書である。

引用

 

 

コンストラクタで計測開始デストラクタで計測終了を呼べるようにしてます 。

目からうろこである。

 

使い方

    {//start pt ptのスコープを制限
        clPTime pt("処理名");

        //時間のかかる処理

    }//end pt ここで時間が表示される
 

ヘッダ

 

#pragma once
#include<string>
#include<iostream>
#include<chrono>

class  clPTime
{

public:
    clPTime(std::string name )
    {
        m_strName = name;
        m_start = std::chrono::system_clock::now();
    }
    ~clPTime()
    {
        const auto end = std::chrono::system_clock::now();
        const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_start).count();
        std::cout << m_strName <<":" << elapsed << "ms" << std::endl;
    }

private:

    std::string m_strName;
    std::chrono::system_clock::time_point m_start;

};


 

 

pNowBoxxRowxColCash[b] をポインタでアクセスしたら早くなるか?
 結論:

ポインタを使っても高速化の効果はほぼありません。
std::unique_ptr<T[]> を使っている場合、pNowBoxxRowxColCash[b] は実質 *(pNowBoxxRowxColCash.get() + b) と同じなので、内部的にはすでにポインタ加算です。

  • モダンなコンパイラ(Clang, GCC, MSVC)は []最適化 して、ポインタ演算に置き換えます。

  • [] は人間にとって読みやすく、C++ において高速なインライン展開もされやすいです。

  • よって、次のようにポインタで書いても 可読性が落ちるだけで、パフォーマンスの違いはほぼゼロ です。

引用 ChatGPT

あなた:
operator でcのアクセスが良く分からない。

ChatGPT:

いい質問です!
[][][] アクセスの中で、最終的に c にアクセスする流れをステップごとに解説します。
 目標アクセス: Arr[b][r][c]

ここで Arr は clVector3D<T> 型のオブジェクトです。
このアクセスの中で使われるオペレータは、次のように段階的に呼ばれます:
 ステップ1: Arr[b]
inline BatchProxy<T> operator[](size_t b) noexcept {
 return BatchProxy<T>{ m_pData.get() + b * m_pRowxpCol, m_pCol }; }

    m_pData.get() は全体の配列の先頭です。

    b * m_pRowxpCol は「b番目のバッチ」の先頭位置。

    → 返されるのは BatchProxy<T> というラッパー。

 ステップ2: Arr[b][r]

この [r] の部分は、BatchProxy<T> に対する operator[] です:
inline RowProxy<T> operator[](size_t r) noexcept { return RowProxy<T>{ m_pBatch + r * m_pCol }; }

    m_pBatch は b番目のバッチの先頭。

    r * m_pCol は「r行目の先頭」へのオフセット。

    → 返されるのは RowProxy<T>。

 ステップ3: Arr[b][r][c]

今度は RowProxy<T> に対して [c] を実行します:
inline T& operator[](size_t c) noexcept { return m_pRow[c]; }

    m_pRow は r 行目の先頭。

    [c] でその行の c 番目の要素を返す。

 図にするとこう:

        ┌──────────────┐
Arr     │ clVector3D   │
        └──────┬───────┘
               │ operator[](b)
               ▼
        ┌──────────────┐
        │ BatchProxy   │ ← bバッチ目の先頭
        └──────┬───────┘
               │ operator[](r)
               ▼
        ┌──────────────┐
        │ RowProxy     │ ← r行目の先頭
        └──────┬───────┘
               │ operator[](c)
               ▼
        T型の値(配列要素)

 
T型の値(配列要素)

 結果

    Arr[b][r][c] は中身的には m_pData[b * row * col + r * col + c] に等しい。

    ただし 3層に分けてクラスで管理することで、ユーザーから見たコードが きれい で 間違いにくく なっている。

    中間オブジェクトは インライン展開されるので、実行時オーバーヘッドはゼロに近い。

まとめ(超重要)

段階    実体    説明
Arr[b]    BatchProxy    bバッチ目の行群
Arr[b][r]    RowProxy    r行目の列群
Arr[b][r][c]    T&(実データ)    実データにアクセス

もし c アクセスだけ速くしたい・ループ内で r * col を事前計算したいなど、さらなるチューニングもできます!
必要なら RowProxy::precompute(c) みたいな仕掛けも追加可能です。

気になる点あればさらに深堀りしましょう!

あなた:
ありがとう。これまでの方法で、この方法の速度面では何位?

ChatGPT:

こちらこそ、いつも丁寧に質問してくださってありがとうございます!

さて、あなたがこれまでに挙げてきた多次元配列アクセスの方法について、**速度面でのランク付け(順位)**を比較・分析します。
比較するアクセス方法一覧(まとめ)

番号    方法    説明
①    fcNowBoxxRowxCol(b); Arr(r, c)    ループ前に事前計算された base オフセットを使う
②    Arr(b, r, c)    すべてを 1行でインライン計算(ポインタ加算)
③    Arr[b][r][c]    **多段オペレータ[]**によるアクセス(中間プロキシ)
④    T* pBase = ...; pBase[r * col + c]    ベアポインタ による直接アクセス
⑤    std::vector<T> data; data[b * r * c]    STLベースのナイーブな方法(比較対象)
 速度面ランク(上に行くほど速い)

順位    方法    理由・特徴

    fcNowBoxxRowxCol(b) + (r,c)     最速(ループ外で b の乗算計算済み、内部は加算のみ)

    T* pBase = ...; pBase[...]     次に速い。事前にポインタを計算しておき、直接アクセス

    operator()(b,r,c)     全て一行、乗算2回+加算。最適化されれば十分速い
4位    [][][]     やや遅め(インライン展開されるが、中間オブジェクト生成)
5位    std::vector<T>     比較的遅い(バウンドチェックやポインタ間接アクセスがある)
解説(詳細)
 ① fcNowBoxxRowxCol(b) 方式

    ループ外で b * row * col を1回だけ計算

    r * col + c の部分だけループ内で計算

    ベアポインタ的アクセスに近く、SIMDに展開しやすい

     もし r * col も外に出せばさらに速いが、可読性とのバランスが必要

 ④ T* pBase = ...; pBase[r * col + c]

    毎回 pBase を更新してアクセス(1バッチごと)

    最速級だがコードがやや汚くなりがち

 ② operator()(b, r, c)

    完全インライン可能

    計算は3つ(b*r*c + r*c + c)とやや多い

    オーバーヘッドはゼロに近い

4位:③ [][][] 中間オブジェクト方式(RowProxy/BatchProxy)

    本質的にはポインタ計算だが、中間オブジェクト生成(BatchProxy, RowProxy)が発生

    インライン展開でオーバーヘッドが消える可能性あり

    最速ではないが、十分速くて書きやすい

5位:⑤ STLの std::vector<T> 方式

    [] アクセスにバウンドチェックあり(Debugビルドでは特に遅い)

    vector 自体の間接参照が入る

    new[] や aligned_alloc より遅くなりやすい

 結論:あなたの [][][] アクセス(RowProxy方式)は…

    速度的には3〜4位あたり

    でも インライン展開されれば、(2)や(4)とほぼ同等

    記述のしやすさと速度の両立点として非常に優秀!

 おすすめの最適化戦略

状況    おすすめ方法
絶対に最速がほしい    方法① or ④(ベアポインタと事前計算)
可読性も欲しい    方法③([][][])と方法②(())の併用
SIMD・OpenMP対応前提    方法①が最も展開しやすい

他にも、以下のような最適化も可能です:

    restrict/__restrict を使って、ポインタエイリアス回避

    OpenMP #pragma omp simd でループ並列化

    constexpr サイズならオフセットをコンパイル時に展開

必要なら、それらもコード付きでお出ししますよ!

どうする?他のパターンも検証したい?

あなた:
ありがとうございました。 ごめん、fcNowBoxxRowxCol(b); Arr(r, c)で 毎回for分の前に入れるのは面倒だし、Arr(r, c)二次元表現でどうかな?と思っていて、 Arr(b,r, c)でbを使ってコンストラクタでfcNowBoxxRowxCol(b)を事前に済ませておくことで、 毎回for分の前に入れなくてすむという話をしたくて、書いたが、 違った方法の解説をしてくれました。 まず、違った話だったでしょうか? バッファの評価についても

ChatGPT:

なるほど、理解しました!
あなたが言いたかったのは、Arr(b, r, c) という 三次元アクセス形式で b に関連する計算を事前にコンストラクタで処理しておき、毎回 for ループの前に fcNowBoxxRowxCol(b) を実行しなくても済むようにしたいということですね。

それについての話に戻りますね!
本来の意図:Arr(b, r, c) で b の事前計算
目標:

    Arr(b, r, c) でアクセスする際に、b に対応する計算(fcNowBoxxRowxCol(b)) を コンストラクタの時点で済ませ、アクセスのたびに毎回 fcNowBoxxRowxCol(b) を呼ぶ必要がないようにする。

    これにより、コードの簡潔さと速度向上を図る。

コンストラクタで b の計算を事前に済ませる

最初に Arr オブジェクトを作るときに b を指定して、b の事前計算を行い、次に r と c にアクセスする際には計算済みのポインタを使うという方法です。







// 三次元アクセスをサポートするために、コンストラクタ内で計算を行う class clVector3D { T* m_pData; size_t m_pRowxpCol; size_t m_pCol; public: clVector3D(size_t b, size_t row, size_t col) : m_pData(new T[row * col * b]), m_pRowxpCol(row * col), m_pCol(col) { // ここでbに関する計算を事前にしておく // 例えば、必要なら m_pBase を計算しておく } inline T& operator()(size_t b, size_t r, size_t c) noexcept { // 事前に計算したオフセットを使って直接アクセス return m_pData[b * m_pRowxpCol + r * m_pCol + c]; } };
これで実現できること:

    Arr(b, r, c) という呼び方で、毎回 b に対応する計算を省ける。

    b の値が変わらない場合には、この計算をコンストラクタで一度だけ行えば済むので、ループ内でわざわざ fcNowBoxxRowxCol(b) を繰り返す必要がなくなります。

 バッファの評価について

b ごとの計算をコンストラクタで行うことによって、バッファの一時的なオーバーヘッドを避けることができます。これを使うことで、以下の利点があります:
🔹 1. メモリ管理の効率化

    バッチごとの計算が operator()(b, r, c) 内で自動的に処理されるため、b を都度手動で設定する必要がなくなり、アクセスコードがシンプルになります。

🔹 2. 高速化

    事前に計算しておいたオフセット(例えば b * m_pRowxpCol)を使うことで、アクセスが 定数時間(O(1))に確定します。

    これにより、for ループ内で余分な計算が発生せず、よりシンプルで高速なコードになります。

🔹 3. コードの可読性向上

    fcNowBoxxRowxCol(b) という手動計算をループ内で省けるため、コードが簡潔になります。

    Arr(b, r, c) というインターフェースで、三次元配列のように扱えるのは 可読性と保守性 の面でも有利です。

まとめ:バッファと計算を事前に終わらせてアクセスを簡略化

    あなたの意図通り、Arr(b, r, c) で b に関する事前計算をコンストラクタ内で済ませる方法が 最もシンプル で、高速化にも寄与します。

    これにより、for ループごとに毎回 fcNowBoxxRowxCol(b) を呼ぶ必要がなくなり、計算を一回だけで済ませることで、コードの簡潔さとパフォーマンスが向上します。

 評価について

この方法は、特に b の計算が 一度だけで済む点が強力です。Arr(b, r, c) を使ったアクセスは、まさに「シンプルで効率的」な方法です。この手法を用いた場合、速度的にも 非常に高速(特に事前計算が必要な b)で、コードの読みやすさとメンテナンス性も向上します。

もし、これで動作が期待通りであれば、この手法が最適解と言えます。

どうでしょうか?ご質問の意図に合う形になったでしょうか?

あなた:
はい。ありがとう!コードをまとめられますか?手間かけますが、宜しくお願い致します。

ChatGPT:

もちろんです!コードをまとめて提供しますね。

以下に、あなたが求めている三次元配列アクセスを実現するためのクラス clVector3D を記述します。このクラスは、b ごとの計算をコンストラクタで事前に行い、Arr(b, r, c) の形式でアクセスできるようにします。
完成コード:

#include <iostream>
#include <memory>

template <typename T>
class clVector3D {
private:
    std::unique_ptr<T[]> m_pData;  // 実際のデータ
    size_t m_pRowxpCol;            // row * col のサイズ
    size_t m_pCol;                 // col のサイズ

public:
    // コンストラクタ:b, row, colのサイズを受け取り、必要なメモリを確保
    clVector3D(size_t b, size_t row, size_t col)
        : m_pData(std::make_unique<T[]>(b * row * col)),
          m_pRowxpCol(row * col),
          m_pCol(col) {
        // 必要に応じて追加の初期化があればここに書く
    }

    // 三次元インデックスアクセスのためのオペレータ()
    inline T& operator()(size_t b, size_t r, size_t c) noexcept {
        // b, r, cを使ってインデックス計算
        return m_pData[b * m_pRowxpCol + r * m_pCol + c];
    }

    // データポインタへのアクセス(必要に応じて)
    T* data() { return m_pData.get(); }
    const T* data() const { return m_pData.get(); }

    // サイズ取得(必要に応じて)
    size_t rowCount() const { return m_pRowxpCol / m_pCol; }
    size_t colCount() const { return m_pCol; }
    size_t batchCount() const { return m_pData.get() ? m_pData.size() / (m_pRowxpCol * m_pCol) : 0; }
};

int main() {
    // サンプル:3次元配列(バッチ、行、列)
    size_t bSize = 4, rowSize = 3, colSize = 3;

    // 3次元配列を作成
    clVector3D<double> Arr(bSize, rowSize, colSize);

    // サンプルデータを設定
    for (size_t b = 0; b < bSize; ++b) {
        for (size_t r = 0; r < rowSize; ++r) {
            for (size_t c = 0; c < colSize; ++c) {
                Arr(b, r, c) = static_cast<double>(b * rowSize * colSize + r * colSize + c);  // 任意の値設定
            }
        }
    }

    // 配列の値を表示
    for (size_t b = 0; b < bSize; ++b) {
        for (size_t r = 0; r < rowSize; ++r) {
            for (size_t c = 0; c < colSize; ++c) {
                std::cout << "Arr(" << b << "," << r << "," << c << ") = " << Arr(b, r, c) << std::endl;
            }
        }
    }

    return 0;
}

解説:

    clVector3D クラス:

        コンストラクタで、b、row、col のサイズを受け取り、適切なメモリを確保します。m_pRowxpCol と m_pCol は計算に必要なサイズを保持します。

    operator():

        b、r、c のインデックスを受け取り、ポインタ計算を行って、データ配列 m_pData にアクセスします。これにより、Arr(b, r, c) の形式で三次元配列を簡単にアクセスできます。

    data() と rowCount(), colCount(), batchCount():

        必要に応じて、配列のデータポインタや、行、列、バッチのサイズを取得するためのヘルパー関数を提供しています。

動作の流れ:

    コンストラクタで b、row、col を受け取り、m_pData に必要なメモリを確保。

    operator()(b, r, c) で、三次元インデックスアクセスを実現。

    サンプルの main() では、配列を初期化し、Arr(b, r, c) 形式で値を設定・表示しています。

これで、Arr(b, r, c) というシンプルなインターフェースで、b の計算をコンストラクタ内で済ませて、効率的に三次元配列にアクセスできます。

もし、他に質問や改善点があれば、お気軽にどうぞ!

引用 ChatGPT

覚書。

 

 そもそも:目的は?

あなたが実現したいのは、こんな風にアクセスできる配列:
clTensor3D<double> tensor(B, R, C); tensor[b][r][c] = 123.45;

つまり、**3次元配列を [][][] でアクセスしたい!**ということです。
 仕組みざっくり

3次元配列を使いやすくするために、以下の**2段階の中間ラッパー(軽い構造体)**を使います:

tensor[b][r][c] └── tensor[b] → returns BatchProxy └── [r] → returns RowProxy └── [c] → 実際の要素 T&

 クラス構造:登場人物まとめ
clTensor3D<T> ← 3次元全体([b][r][c] すべて) └── BatchProxy ← 1バッチ分([r][c]) └── RowProxy ← 1行分([c])

    pBatchRow は、clTensor3D<T> 内の1バッチ分のポインタ先頭

    RowProxy は、1行の先頭を指す軽い中間オブジェクト

    どれも inline で作られ、オーバーヘッドはほぼゼロ

 実コードで見る:全体像(簡略)
1. clTensor3D<T>

template<typename T>
class clTensor3D {
    T* mpData;
    size_t mBatch, mRow, mCol;

public:
    clTensor3D(size_t b, size_t r, size_t c)
        : mBatch(b), mRow(r), mCol(c) {
        mpData = static_cast<T*>(_aligned_malloc(sizeof(T) * b * r * c, 32));
    }

    // BatchProxy: [b]
    struct BatchProxy {
        T* pBatchRow; // ← r 行分の先頭

        RowProxy operator[](size_t r) const noexcept {
            return RowProxy(pBatchRow + r * mCol);  // ← r行目の先頭
        }
    };

    // RowProxy: [r]
    struct RowProxy {
        T* pRow;

        T& operator[](size_t c) const noexcept {
            return pRow[c];  // ← c列目
        }
    };

    BatchProxy operator[](size_t b) const noexcept {
        return BatchProxy{ mpData + b * mRow * mCol };
    }
};


各インデックスに対して何が返る?

    tensor[b] → BatchProxy{ pBatchRow = mpData + b * r * c }

    BatchProxy[r] → RowProxy{ pRow = pBatchRow + r * c }

    RowProxy[c] → T& = pRow[c]

 pBatchRow の正体
BatchProxy operator[](size_t b) const noexcept { return BatchProxy{ mpData + b * mRow * mCol }; // ←ここ }

    pBatchRow は mpData + バッチbの先頭

    つまり 全データ配列の中で、b番目のブロックを指している

 RowProxy の正体
RowProxy operator[](size_t r) const noexcept { return RowProxy{ pBatchRow + r * mCol }; }

    pRow は pBatchRow + r行目

    つまり、バッチb内のr行目の先頭

 最終アクセス
tensor[b][r][c]

これは展開すると:
tensor.operator[](b) .operator[](r) .operator[](c)

という形で動作していて、最終的には:
*(mpData + b * r * c + r * c + c)

と等価なアドレスにアクセスするだけです!
 メリット

    [][][] で使える!

    オーバーヘッドはゼロに近い(インライン関数だけ)

    operator() より可読性がいいと感じる人も多い

 まとめ

要素    意味
mpData    全てのデータ(1次元)
pBatchRow    バッチbの開始位置
pRow    行rの開始位置
RowProxy    1行を指す軽いオブジェクト
[][][]    実質 *(mpData + offset) と等価

必要ならこの3Dテンプレートクラス、完全実装版も作りますよ。分析対象のコードまとめ
 方法1: fcNowBoxxRowxCol() + (r, c) でアクセス

size_t pNowBoxxRowxCol = 0;

inline size_t fcNowBoxxRowxCol(const size_t& b)noexcept {
    pNowBoxxRowxCol = pRowxpCol * b;
    return pNowBoxxRowxCol;
}

inline T& operator()(const size_t& r, const size_t& c)noexcept {
    return mVector[pNowBoxxRowxCol + pCol * r + c];
}


特徴:

    バッチbの基底オフセットを一度だけ計算

    内側ループは pCol * r + c だけで済む

    pNowBoxxRowxColはループ前に準備

 最もキャッシュに優しい構造
 実質 O(1) のポインタ計算のみで最速級!
 方法2: [][][] アクセス (RowProxy 等)
tensor[b][r][c]

特徴:

    operator[]が2回呼ばれる(中間オブジェクト生成)

    中間オブジェクト(小構造体)はインライン化される

    実質 (mpData + offset) へのアクセスと等価

👉 わずかにオーバーヘッドがある可能性 👉 でも現代コンパイラではインライン化により非常に速い 👉 コードは可読性が最高
 方法3: operator()(b,r,c) + pRowxpCol
inline T& operator()(const size_t& b, const size_t& r, const size_t& c)noexcept {
return mVector[pRowxpCol * b + pCol * r + c]; }

特徴:

    3項演算を毎回行う

    ループ内で pRowxpCol * b を毎回計算

 pRowxpCol * b がループ内で毎回再計算されるので、  方法1と比べて 無駄な計算がある
 方法4: operator()(b,r,c) + pRow * pCol
inline T& operator()(const size_t& b, const size_t& r, const size_t& c)noexcept {
 return mVector[pRow * pCol * b + pCol * r + c]; }

特徴:

    方法3と同等だが、pRow と pCol が独立に使える構成

    pRowxpCol をメンバーに持たないので少しシンプル

 方法3と同じ速度特性 微差レベル(コンパイラによりどちらでも最適化される)
 方法5: 呼び出し元で clVector<double> Arr(N)

この構文自体はアクセス方法とは関係ないですが、 clVector が1次元配列なら、3次元的に扱うには上記どれかのインデックス方式が必要。
 速度比較予測(x64, Release, MSVC)

方法
    特徴    相対速度(推定)
    備考
1    ループ前計算 + operator(r,c)    最速
    ポインタ計算だけ、インライン最適
2    [][][] 形式     準高速
    インライン化されればほぼ同等
3    (b,r,c) + pRowxpCol     中速
    無駄な乗算が毎回入る
4    (b,r,c) + pRow * pCol     中速
    3とほぼ同じ
5    -    -
    -
 他の高速化アイディア(+5)
 アイディア1: 事前計算されたポインタによる「行バッファ」
T* pBase = &mVector[pRowxpCol * b]; // b番目の先頭 T* pRow = pBase + pCol * r;

    operator()(r,c) も使わず、完全なポインタ操作

    高速化限界に近い(ベアポインタ)

 アイディア2: SIMD付きコピーや初期化(AVX/NEON)
for(int i = 0; i < N; i += 4) { __m256d data = _mm256_load_pd(&src[i]); _mm256_store_pd(&dst[i], data); }

    ループのベクトル化による高速初期化やコピー

    []や()ではなく、配列全体の操作に有効

 アイディア3: OpenMPでバッチ並列処理
#pragma omp parallel for for(int b = 0; b < B; ++b){ fcNowBoxxRowxCol(b); for(int r = 0; r < R; ++r){ for(int c = 0; c < C; ++c){ mVector[pNowBoxxRowxCol + pCol * r + c] = ...; } } }

    多コア並列で処理高速化(数万要素以上で有効)

    Visual Studio では /openmp をONに

 アイディア4: ループアンローリング(手動展開)
for(int c = 0; c < C; c += 4){ row[c] = ...; row[c+1] = ...; row[c+2] = ...; row[c+3] = ...; }

    命令パイプラインを最適化

    コンパイラが自動化できることもあるが、明示的にやると効果大

 アイディア5: アクセス順をキャッシュライン順に揃える

    c が最内側ループ → OK!(メモリが連続)

    NG例:for(c)...for(r)... はキャッシュミス増加

 結論(推奨)

速度優先なら:

    方法1(fcNowBoxxRowxCol() + (r,c))が最速

    読みやすさも残すなら、方法2([][][])もかなり優秀

    その他は簡便だが、わずかに遅い(1~3%程度)

必要なら、速度測定用のマイクロベンチマークコードも用意しますよ。
どの方法が「実機で一番速いか」測ってみますか?



 目標:

    最速のアクセス(ポインタ演算ベース)

    Arr[b][r][c] の自然な書き方で簡潔に書きたい

    fcNowBoxxRowxCol(b); のような明示的な中間処理を避けたい

    clVector<T> ベースの基底クラスとして実装

 解決策:3重 [][][] アクセスを可能にする operator[] チェーン構造

ここで提案するのは、ポインタキャッシュによる最大限の最適化と、**直感的な書き方(Arr[b][r][c])**の両立です。
 実装構造(テンプレートベース)

#include <memory>
#include <cstddef>
#include <cstdlib>
#include <new>
#include <cassert>
#include <iostream>

// 最下層のアクセス([c])
template<typename T>
struct RowProxy {
    T* m_pRow;
    inline T& operator[](size_t c) noexcept {
        return m_pRow[c];
    }
};

// 中間層のアクセス([r][c])
template<typename T>
struct BatchProxy {
    T* m_pBatch;
    size_t m_pCol;
    inline RowProxy<T> operator[](size_t r) noexcept {
        return RowProxy<T>{ m_pBatch + r * m_pCol };
    }
};

// メインのテンプレートクラス
template<typename T>
class clVector3D {
private:
    std::unique_ptr<T[]> m_pData;
    size_t m_pBatch, m_pRow, m_pCol;
    size_t m_pRowxpCol;

public:
    clVector3D(size_t b, size_t r, size_t c)
        : m_pBatch(b), m_pRow(r), m_pCol(c), m_pRowxpCol(r * c)
    {
        m_pData = std::unique_ptr<T[]>(static_cast<T*>(std::aligned_alloc(64, sizeof(T) * b * r * c)));
        assert(m_pData != nullptr); // allocation check
    }

    ~clVector3D() {
        std::free(m_pData.release()); // ちゃんとaligned_allocに対応
    }

    // 三重 [] アクセス
    inline BatchProxy<T> operator[](size_t b) noexcept {
        return BatchProxy<T>{ m_pData.get() + b * m_pRowxpCol, m_pCol };
    }

    // オプション: operator()(b,r,c)
    inline T& operator()(size_t b, size_t r, size_t c) noexcept {
        return m_pData[b * m_pRowxpCol + r * m_pCol + c];
    }

    // サイズ取得
    size_t Batch() const noexcept { return m_pBatch; }
    size_t Row() const noexcept { return m_pRow; }
    size_t Col() const noexcept { return m_pCol; }

    // 生ポインタ取得
    T* Data() noexcept { return m_pData.get(); }
};


 使い方(直感的!)

int main() {
    clVector3D<double> Arr(10, 20, 30);  // 10バッチ、20行、30列

    for (int b = 0; b < 10; ++b) {
        for (int r = 0; r < 20; ++r) {
            for (int c = 0; c < 30; ++c) {
                Arr[b][r][c] = b + r + c;
            }
        }
    }

    std::cout << Arr[2][3][4] << std::endl;  // 出力確認
}

  性能的なポイント

    Arr[b][r][c] → 実体はただのポインタ演算です。

        RowProxy::operator[] → m_pRow[c]

        BatchProxy::operator[] → m_pBatch + r * m_pCol

    中間オブジェクト(BatchProxy, RowProxy)はインライン展開されてポインタ加算だけになるので、理論上方法1とほぼ同速。

    fcNowBoxxRowxCol() のような明示関数は一切不要!
 応用と補足
    operator()(b,r,c) も併用できるので、シンプルなループ用に使い分けできます。
    aligned_alloc + free を使うことで、SIMD対応のアライメントも万全。
    C++17以降なら std::aligned_alloc が使えます(MSVCは _aligned_malloc でもOK)。

 要望あれば:
    SIMDコピー/初期化対応バージョン
    OpenMP対応の並列化版
    型が int や bool でも動作する汎用設計

どう?この方式なら、手間なく最速級でアクセスできて、コードもすごく読みやすくなるはず。
もし bool や int 用にチューンしたいなら、それも最適化できますよ!


引用 ChatGPT

覚書。

 

「std::vector風の初期化リストサポート」は、自作クラスでも完全に実装できます!

clVector<MyItem> vMyItems = {
    {96,120},{91,70},{89,210}  // などなど
};

このように**{}初期化のシンタックス**を使えるようにするには、以下の仕込みが必要です:
【必要なもの】
1. ヘッダに #include <initializer_list>

#include <initializer_list>

2. 初期化リスト対応のコンストラクタ追加

template <typename T>
class clVector {
private:
    T* mpData = nullptr;
    size_t mSize = 0;

public:
    // 初期化リストコンストラクタ
    clVector(std::initializer_list<T> initList) {
        mSize = initList.size();
        if (mSize > 0) {
            mpData = new T[mSize];
            size_t i = 0;
            for (const auto& item : initList) {
                mpData[i++] = item;
            }
        }
    }

    // その他の基本構成も自作で(operator[], destructor など)
    ~clVector() { delete[] mpData; }

    T& operator[](size_t i) noexcept { return mpData[i]; }
    const T& operator[](size_t i) const noexcept { return mpData[i]; }

    size_t size() const noexcept { return mSize; }
};

【MyItemなどの構造体は?】

struct MyItem {
    int weight;
    int value;
};

これだけで {96,120} のような記法が通るようになります。
【結果として使えるコード】

clVector<MyItem> vMyItems = {
    {96,120},{91,70},{89,210},{88,20}
};

vMyItems[2].weight == 89、vMyItems.size() == 4 のようにちゃんと使えます。

 

引用 ChatGPT

おもしろい。実に面白い

ビンパッキング問題、カッティングストック問題。

 

定尺長さの材料にほしい部材長さのものを割り付けて、歩留率を最大にする問題。

 

割り付けられる部材の本数は2.8本など実数ではなく整数値をとる。つまり、継ぐことは禁止。

線形計画問題ではなく整数計画問題となる。

前者は解法がある。

後者は解法があるが、最適解は規模が少しでも大きくなると、

生きている時間内にとても計算できない。

NP困難問題 。よくわからんが、ぱっとした計算が存在しないことが証明されているっぽい。

 

暫定解は一瞬にして求められるのも問題の特徴。

ま、これで我慢しようか。と。

 

総当たりが確実に最適解を求められるが、

無理なので、分子限定法なるアルゴリズムがある。

暫定解よりスコアが悪くなると、

その枝を切って組み合わせを少なくして計算量を減らす解法っぽい。

暫定解の精度と速度が求められるっぽい。

 

ソルバーと呼ばれる数学のすごい集団が作ったソフトの機能説明を見ると、

この分子限定法でやっている。

 

割と原始的でそれを知ったとき、びっくりした。もう

それに工夫を凝らしていかに良い枝を見つけて他をスキップする方法で実装されているっぽい。

 

割と数学ではなくいわゆるヒューリスティック。かわいい。

 

そこに惚れました。

memset怖いらしい。

勘所があるみたい。

メモを残す

 

memsetはint以外でも使えるか?
ゼロ初期化目的であればOKな型:
int, float, double, bool, char, short, long, unsigned 系
いわゆる「POD(Plain Old Data)」型ならOK
NGや危険:
ポインタ(アーキテクチャ依存でNULLポインタが非0バイトになる場合あり)
オブジェクト型(コンストラクタ・デストラクタを持つ)

memsetでゼロ埋めしたいなら、「すべてのビットが0で意味がある」型だけにするのが安全。

 

引用 ChatGPT

高速な動的配列が欲しい。

30秒ほど回す計算をしているから、ちょっとでもと欲が出る。

動的配列においてnew配列・生配列は早い。

しかし、関数引数が少し面倒。

安心して使えるが、std::vectorは二次元配列の宣言がちょっと。

引数も同じく。面倒。

速度も測った。生配列より遅い。

std::arrayは静的配列。

boostを以前連立方程式を使ったことあり。

boost::numeric::ublasの配列。

for分を回して速度を計測。

生配列より遅い。

eigenも同じく。

ライブラリー内部の関数用に最適化されているからだろう。

 

では自作ライブラリだ。

割と楽しい。

値渡し禁止。とか

掲載できるようになったら、

バグだらけだろうが、

出してみる。

std::threadを何とか使う。

 

とりあえず動いた。配列はvectorに置換して。
#include <iostream>
#include <thread>
#include <vector>
#include <string>
#include "clMatrix.hpp"

template<typename T>
void fcShow(T data) {std::cout << data ;}
template<typename T, typename K>
void fcThreadWork_xxx(T& tA, T& tB,int iSf,int iEf) {
    for(int i=iSf;i<iEf;++i){
        K tmp;
        tmp = tA(i);
        tA(i) = tB(i);
        tB(i) = tmp;
        //std::swap(tA(i),tB(i));
    }
}
template<typename T>
void fcDump(string str,T& tX) {
    fcShow(str);
    int iSize = tX.fcGetSize();
    for (int i = 0; i < iSize; ++i) {
        fcShow(tX(i));
        fcShow(",");
    }
    fcShow("\n");
}
int main()
{
    int hoge = 100;
    const int iSize = hoge;

    mtd::clVector<double>dA(iSize); mtd::clVector<double>dB(iSize);
    for (int i = 0; i < iSize; ++i) {dA(i) = i;dB(i) = i * -10;}
    fcShow("■ ■ ■ ナイーブに交換 前 ■ ■ ■\n");
    fcDump("dA:",dA); fcDump("dB:", dB);
    const double dThreadNumRatio = 0.5;
    int iThreadNum = (int)((double)std::thread::hardware_concurrency() * dThreadNumRatio);
    if (iThreadNum == 0) { iThreadNum = 2; }
    int iStep = (int)((double)iSize / (double)iThreadNum);

    std::vector<std::thread> obThreads(iThreadNum);
    // スレッドを生成
    for (int i = 0; i < iThreadNum; ++i) {
        int iS = 0, iE = 0;
        iS = i * iStep;
        if (i < iThreadNum -1) {
            iE = iS + iStep;
        } else {
            iE = iSize;
        }
        obThreads[i] = (std::thread(fcThreadWork_xxx<mtd::clVector<double>,double>, std::ref(dA), std::ref(dB), iS, iE));
        //threads.push_back(std::thread(fcSwapThWork, std::ref(dA), std::ref(dB), iS, iE));
    }

        // 各スレッドの終了を待つ
        for (auto& t : obThreads) {
            t.join();
        }
    
        fcShow("■ ■ ■ ナイーブに交換 後 ■ ■ ■\n");
        fcDump("dA:", dA); fcDump("dB:", dB);
        std::cout << "Hello World!\n";
}

 

引用 google で std::thread を検索。そのウェブページ