過去40年分のデータダウンロード時間

JV-LINKでデータ取り込んでいます。

2016年からの10年分は3時間程度で取り込めるのですが、2006年からの10年分は8時間30分程度かかります。

古いデータになるほど転送速度が遅い環境にあるのでしょうか?

Kamiさん、こんばんは。

なにでどのようにJV-Linkから取り込んでいるのかは不明ですが、自分が当初Visual BasicでSQLiteにデータベースとして取り込んでいる時に1986年からの全てを行うと14時間とかそんな感じでしたが、今はコードを見直したりでうちのPC環境下(7年前に組んだのでたいした環境ではないCore i7の)では1時間程度にまで短縮してます。これ、単純にテキストとしてだと30分程度だったかと記憶してるので、JV-LinkがではなくてKamiさんが組んだコードが遅いだけかと。

年によって時間が変わるのは何かしらデータが増えたりとかもあるし、原因はあるんだと思うけど、基本的には昔は三連馬券とか無かったり、坂路調教等が提供されてない、出走別着度数の提供などで変わるとは思うけど、年々遅くなる、つまり、データ量が増える感じだったかと。

さすがに1時間は無理ではないでしょうか?

一度ダウンロードしたデータ(例えばC:\ProgramData\JRA-VAN\Data Lab)から読み込んでいるとか。
Targetや他Postgresやmdbデータベースの取り込むアプリでも1日以下で取得できるソフトを見たことがありません。あれば紹介してください。

JVSKIPコマンドでダウンロードした後に読み飛ばすことはできるがダウンロード時間はかわらない。DBに取り込む時間は短縮できるけど。

Kamiさん、おはようございます。

これ、別にアプリがではなく、JV-Link自体が確かにキャッシュを使います。既に一度ダウンロードしていれば次はダウンロード無でデータが供給されますので当然早い。でも、現在では多分一般的になっている光回線ならうちは1ギガしか契約してませんが、データベースに蓄積している処理時間より短いと思うけど。

以前こちらの旧掲示板でやり取りした時に自分のアプリが14時間掛かるって相談した際に早い方は30分程度と言われた記憶があります。自分のアプリは色々アドバイスを参考に改善した結果、2~3時間に短縮してアプリを公開。最近のバージョンアップでは1時間程度になりました。

参考までに、当然

スキップとかせずに、全てのデータを取込んでいます。出来上がるデータベースは15GB弱です。

一応念のため、先程6:12分にキャッシュ削除してからフルセットアップ開始させました。

当時言われたアドバイスで記憶しているのはデータ取得期間を例えば一括で指定、つまり、1986年から現在まで全て一括にするとJV-Link側が遅くなるので分割して指定する方が早いとの話です。自分は1986年から1か月毎に分割して取得してます。

自分のアプリのユーザー様からは8時間程度掛かる環境の方、自分と同じ程度の1時間程度の方と報告は頂いております。自分のテストでもメモリが少ない環境とかだと時間の掛かる環境はあると認識しており、テスト的に構築した仮想マシンで8GB程度のメモリだと8時間程度掛かってました。うちの環境はCore i7 8700 CPUに64GBメモリ、SSDは起動ドライブ用につけてますが、アプリを含めアプリのデータベースはHDDに入れてます。SSDにデータベースを構築すれば更に高速化は可能だとは思うけど、その必要性を感じてないので試してません。これ書いている間に2009年とかまで進んでますが、後で終了時間を報告しますね

自分のアプリは「さらだ」ですが、他の方のアプリを全て把握してる訳ではなく、ターゲットはJRA-VANの推薦アプリですが、これは骨董品で数日セットアップに掛かるのは認識しており、だから公式はターゲットのみほぼフルなデータを付属提供許可してますね。

回答ありがとうございます。

>1986年から1か月毎に分割して取得してます。
試してみます。

ただし開始日は指定できますが、終了日は指定できないのでユニット毎に取り込んで終了日まで取り込んでいるか確認する必要があると思います。

JVOpen メソットで分割して取り込めるのでしょうか?ぜひ知りたいです。

環境は
CPU:Xeon W-2145 8コア
メモリ:128GB
SSD:メモリをRAMDISKとして利用(M2.SSDの2倍は早い)
回線:1G

の贅沢仕様です。

使って確認してみます。

SaraD すばらしい! 小分けにして処理がどんどん進んでますね。
希望が持てました!ありがとうございます。

Kamiさん、今、フルセットアップが終わりました。

キャッシュ削除して他のアプリは今は特に稼働させてなかったせいか、ご覧の時間で終わりました。今回はだいぶ早かったな^^ 前回試した時には裏で機械学習させたりしてたのでこれより10分程度長かった。

もう一つ忘れてたのは、取得する際にまとめてだと遅いので個別にしてます。RACE、BLDN、MING等です。で、インターフェース仕様書のJVOpenの所にも記載がありますが、終了を指定できないとされているデータ種別IDは期間指定可能なものを全て取得した後にそれぞれを取得してます。

で、一応JVReadではなくJVGetsにしてます。今はC#なので、JVGets使用時はちと面倒です。

まあ、自分のアプリを実際に動かして頂ければ分かるとは思うけど、

凄い環境ですね、うらやましいです。

これだけ教えていただければ十分です。真似してみます。

本当ですね30分で終わりました。それも票数やオッズまで取得してるなんて。すぐ真似させていただきます。

Kamiさん、試して頂きありがとうございます。

流石に環境が良いと早いですね。

ご指摘の票数とオッズなんですが、実はこれも若干手抜きしてたりします。当初の計画ではどちらも親テーブルに子テーブルをぶら下げる仕様を考えてました。しかし、それでは非常に遅くなります。当然ですよね、三連単とかでは子テーブルがどれだけの数が毎レースと考えればね。そこでJV-Linkの提供データのまま格納してます。つまり、データ使用時に例えば、今日の中山1Rのオッズ表示時に個々のオッズに分解して表示するようにしてます。

それにしても同じC# 同じSQLiteで

2026/04/05 8:07:01 レース詳細(418) [1986/01] >> Done! …0.0223667秒

→ 一ヵ月分を0.0223667秒 異常ですね。

2026/04/05 8:07:01 馬毎レース情報(3,226) [1986/01] >> Done! …0.099072秒

→ 一ヵ月分を0.099072秒 異常ですね。

RACE: 終了!
2026/04/05 8:07:01 コミット開始!.. >> Done! …00:00:00.0205269

→コミットの速さも異常ですね。

→RACE、BLDN、MING、SNPN、SLOP、WOOD、YSCHの有無確認のスピードも速い

参考になります。

JVGets ではなく JVReadのままですが1時間で取得できる目途がつきました。
サンプルを参考にしていたら一生気が付かなかったと思います。

これなら全データを取得したソフトを作成できそうです。
C# & SQLite で行けそうです。

ありがとうございました。

Kamiさん、こんばんは。

お役に立てたなら光栄です。JVGetsはC#では曲者なのでJVReadで問題なしでしたらOKだと思います。

Good luck!

いろいろためしましたが

RACE のダウンロードを
JVGetsを使って開始日を指定して1か月でいったん終了して繰り返し取得しています。
RACEだけで49分くらいかかっています。前半はそこそこ早いですが後半のDLがと特に遅いです。

SLOPもWOODも時間かかって結局 RACE,SLOP,WOOD,DIFF,KISHで2時間くらいかかってしまってます。

やっぱり全てのデータを取得して30分って凄すぎますね!

前半
18:45:38 - 1986/9/28から1986/10/28 RACE取得中
18:45:39 - (DL:1742ms,Parse:26ms,DB:54ms) Race:2,620, Uma:27,291, Hara:2,464, Chokyo:0
18:45:39 - 1986/10/28から1986/11/27 RACE取得中
18:45:41 - (DL:1833ms,Parse:38ms,DB:56ms) Race:2,897, Uma:30,178, Hara:2,741, Chokyo:0
18:45:41 - 1986/11/27から1986/12/27 RACE取得中
18:45:44 - (DL:2373ms,Parse:33ms,DB:76ms) Race:3,252, Uma:34,173, Hara:3,096, Chokyo:0
18:45:44 - 1986/12/27から1987/1/26 RACE取得中
18:45:45 - (DL:1461ms,Parse:30ms,DB:50ms) Race:3,440, Uma:36,557, Hara:3,284, Chokyo:0
18:45:45 - 1987/1/26から1987/2/25 RACE取得中
18:45:47 - (DL:1771ms,Parse:29ms,DB:54ms) Race:3,667, Uma:39,392, Hara:3,511, Chokyo:0

後半
19:22:18 - 2025/7/5から2025/8/4 RACE取得中
19:22:24 - (DL:5819ms,Parse:73ms,DB:167ms) Race:235,741, Uma:2,817,817, Hara:135,735, Chokyo:0
19:22:24 - 2025/8/4から2025/9/3 RACE取得中
19:22:33 - (DL:8384ms,Parse:67ms,DB:124ms) Race:236,255, Uma:2,824,140, Hara:136,095, Chokyo:0
19:22:33 - 2025/9/3から2025/10/3 RACE取得中
19:22:38 - (DL:5251ms,Parse:64ms,DB:116ms) Race:236,706, Uma:2,829,374, Hara:136,335, Chokyo:0
19:22:38 - 2025/10/3から2025/11/2 RACE取得中
19:22:44 - (DL:5823ms,Parse:85ms,DB:133ms) Race:237,216, Uma:2,835,539, Hara:136,599, Chokyo:0
19:22:44 - 2025/11/2から2025/12/2 RACE取得中
19:22:51 - (DL:6440ms,Parse:67ms,DB:134ms) Race:237,738, Uma:2,841,956, Hara:136,911, Chokyo:0
19:22:51 - 2025/12/2から2026/1/1 RACE取得中
19:22:57 - (DL:6378ms,Parse:73ms,DB:159ms) Race:238,304, Uma:2,848,894, Hara:137,175, Chokyo:0
19:22:57 - 2026/1/1から2026/1/31 RACE取得中
19:22:57 - (DL:121ms,Parse:0ms,DB:0ms) Race:238,304, Uma:2,848,894, Hara:137,175, Chokyo:0

19:24:57 - 2004/6/29から2004/7/29 SLOP取得中
19:25:14 - (DL:17070ms,Parse:79ms,DB:507ms) Race:240,146, Uma:2,872,119, Hara:138,081, Chokyo:302,733
19:25:14 - 2004/7/29から2004/8/28 SLOP取得中
19:25:22 - (DL:7577ms,Parse:45ms,DB:260ms) Race:240,146, Uma:2,872,119, Hara:138,081, Chokyo:324,679
19:25:22 - 2004/8/28から2004/9/27 SLOP取得中
19:25:28 - (DL:5619ms,Parse:27ms,DB:189ms) Race:240,146, Uma:2,872,119, Hara:138,081, Chokyo:340,014
19:25:28 - 2004/9/27から2004/10/27 SLOP取得中
19:25:41 - (DL:12914ms,Parse:59ms,DB:395ms) Race:240,146, Uma:2,872,119, Hara:138,081, Chokyo:376,075
19:25:41 - 2004/10/27から2004/11/26 SLOP取得中
19:26:02 - (DL:20217ms,Parse:107ms,DB:632ms) Race:240,146, Uma:2,872,119, Hara:138,081, Chokyo:432,939
19:26:02 - 2004/11/26から2004/12/26 SLOP取得中
19:26:22 - (DL:18861ms,Parse:110ms,DB:570ms) Race:240,146, Uma:2,872,119, Hara:138,081, Chokyo:485,104
19:26:22 - 2004/12/26から2005/1/25 SLOP取得中
19:26:41 - (DL:18093ms,Parse:99ms,DB:629ms) Race:240,146, Uma:2,872,119, Hara:138,081,

Kamiさん、こんばんは。

いや、KamiさんのPCが凄すぎですよw ってのは置いておいて^^

ざっくりと見てないままでの話です。

JVGetsを試してるのかと思いますが、これ、マジで曲者です。Visual Basicでは簡単に開放出来るみたいですが、C#では以前の掲示板でも話がありましたが、面倒なんですよ。インターフェース仕様書にもVisual Basicの話は掛かれてますよね。

Erase bytData ‘読み込みで確保されたメモリを明示的に削除します

これがC#では実装されてないのが問題で…

    // JVGets用
    [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr SafeArrayCreateVector(VarEnum vt, int lLbound, uint cElements);
    [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern uint SafeArrayDestroy(IntPtr pSafeArray);
    [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern uint SafeArrayAccessData(IntPtr pSafeArray, ref IntPtr ppvData);
    [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern uint SafeArrayUnaccessData(IntPtr pSafeArray);
    [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern uint SafeArrayGetUBound(IntPtr pSafeArray, uint nDim, ref int lUbound);

この定義がまず必要になります。で、

                    // JVGets で1行読み込み
                    iReturnCode = SaraD.clsJVLink.JVGets(ref objBuff, iBuffSize, out strFileName);
                    if (iReturnCode > 0)
                    {
                        bBuff = new byte[iReturnCode];
                        IntPtr pSafeArray = (IntPtr)(int)objBuff;
                        try
                        {
                            uint hr;

                            IntPtr ppvData = (IntPtr)0;
                            hr = SafeArrayAccessData(pSafeArray, ref ppvData);
                            if (hr != 0) throw new System.Exception(hr.ToString());

                            Marshal.Copy(ppvData, bBuff, 0, iReturnCode);
                            // strBuff = Marshal.PtrToStringAnsi(ppvData);

                            SafeArrayUnaccessData(pSafeArray);
                        }
                        finally
                        {
                            SafeArrayDestroy(pSafeArray);
                        }
                    }

って感じにする事で

また、JVGets ではメモリの解放を行わないので、アプリケーション側で読み出しの度に解放する必 要があります。

が実現出来るという事らしいです。これ、らしいってのは自分はあまり理解してないのですが、ここの旧掲示板で指導して頂いた方の話から単にマネさせて頂いています。

これがもしかするとKamiさんのJVGetsでのパフォーマンスに影響してるんじゃないかと思います。

詳細把握してないけど、これが自分の知識なので少しでも参考になれば幸いです。

Kamiさん、追記です。

これ、自分も最初は随分と時間が掛かりました。レコード数が半端ないのが原因なんですよね。これも実は旧掲示板でアドバイス頂いてました。

SQLiteでデータを登録する際に、ごめんない、言葉を忘れてるので適切な説明にならないかもですが、基本的にCommandTextを設定してParametersを準備しますよね? このパラメーターを毎回準備するのは効率が悪いっ話らしいですよ。で、自分は事前にParameter.Addで必要なパラメーターの定義は済ませて、実際に使う時にはパラメーターに値をセットするようにしてPrepareを呼び出して実行してます。

ちと、説明が分かり辛いと思うけど、ざっくりな話としてはそんな感じにする事で効率アップしてますよ。

つまり、

        _HCcmdINSERT = new SQLiteCommand(_con);
        _HCcmdINSERT.CommandText = JVData.HC_UPSERT_TEXT;
        _HCcmdINSERT.Parameters.Add("@T", System.Data.DbType.Byte);
        _HCcmdINSERT.Parameters.Add("@CDT", System.Data.DbType.DateTime);
        _HCcmdINSERT.Parameters.Add("@KETTO", System.Data.DbType.Int64);

        _HCcmdINSERT.Parameters.Add("@FOURT", System.Data.DbType.Single);
        _HCcmdINSERT.Parameters.Add("@FOURL", System.Data.DbType.Single);
        _HCcmdINSERT.Parameters.Add("@THREET", System.Data.DbType.Single);
        _HCcmdINSERT.Parameters.Add("@THREEL", System.Data.DbType.Single);
        _HCcmdINSERT.Parameters.Add("@TWOT", System.Data.DbType.Single);
        _HCcmdINSERT.Parameters.Add("@TWOL", System.Data.DbType.Single);
        _HCcmdINSERT.Parameters.Add("@ONEL", System.Data.DbType.Single);

        _HCcmdINSERT.Parameters.Add("@DDT", System.Data.DbType.DateTime);

としておいて、使う時に

            // 挿入
            var p = _HCcmdINSERT.Parameters;

            p[0].Value = t;
            p[1].Value = cdt;
            p[2].Value = ketto;

            // 調教タイム(整数部 + 小数部)
            p[3].Value = ParseFloat(Combine(s.Slice(34, 3), s[37]));
            p[4].Value = ParseFloat(Combine(s.Slice(38, 2), s[40]));
            p[5].Value = ParseFloat(Combine(s.Slice(41, 3), s[44]));
            p[6].Value = ParseFloat(Combine(s.Slice(45, 2), s[47]));
            p[7].Value = ParseFloat(Combine(s.Slice(48, 3), s[51]));
            p[8].Value = ParseFloat(Combine(s.Slice(52, 2), s[54]));
            p[9].Value = ParseFloat(Combine(s.Slice(55, 2), s[57]));

            // 情報作成年月日
            p[10].Value = ParseDate(s, 3, 7, 9);

            _HCcmdINSERT.Prepare();
            _HCcmdINSERT.ExecuteNonQuery();

という感じです。

追記
こんなに自分のコード晒して良いのかは…

ソースまで公開していただき感謝しかないです。土日に思う存分楽しませていただきます。結果は報告させていただきます。

Kamiさん、おはようございます。

ちょっと抜けた説明になってました。JVGetsに渡すojbBuffとstrFileNameですが、事前に

                // JVGetsとSafeArray関連
                strFileName = new string('\0', iNameSize);
                object objBuff = (object)(IntPtr)0;

としてます。

データ登録時の話なんですが、バイト配列をJVGetsから得たデータとしてデータベースに登録する際に

        ReadOnlySpan<byte> s = bBuff;

として、このReadOnlySpanを処理する

    // --- ヘルパーメソッド群 ---
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte Parse2(ReadOnlySpan<byte> s)
    {
        if ((uint)(s[0] - '0') > 9) return 0;
        return (byte)((s[0] - 0x30) * 10 + (s[1] - 0x30));
    }

みたいな感じでParse3~Parse15まで必要なものを準備しておく。更に上にもあるParseFloatやParseDate等も。実は、これ、これ以前は直接バイト配列からEncoding.GetEncoding(“Shift_JIS”)使って個々に処理するのをAI(これはCopilot)に指導されて修正したのですが、これによって、フルセットアップが2時間程度掛かっていたのが現在の1時間切るものになりました。

なので、最初は自分なりにコードを書いて、AIに自分のコードを見せて「これを最適化してください」とお願いするのもひとつの手だと…時代ですね(笑)