R02/07/10公開
R02/07/15 ソースコード等追加
R02/07/18スクリプトを訂正
「chunkSize = 3000 * pow(10, 5) # 300,000,000」を「chunkSize = 1500 * pow(10, 5) # 150,000,000」に
MacPro(Late 2013) macOS Catalina10.15.5(19F101)
Thunderbolt を3 本接続。接続経路は下記のリンク先の3 つあるThunderbolt Bus に各1本づつ接続。
インターネット共有を使用して、Thunderbolt Bridge を3 本独立で接続。(Thunderbolt Bus にご注意ください。同一のBus で1 本づつしか接続できないようです。参考
https://support.apple.com/ja-jp/HT202801
)
hosts の設定は今回は影響なかったです。今回のコピー対象ファイルは、
23,319,140,825byte
です。複数回試しました。およその結果が下記。
コピー時間、
84.78s 換算2.049Gibps
単純にcp コマンドでコピーした際には、
117s 換算1.485Gibps
でした。
事前にファイルを3 分割してcp コマンドで同時コピー(cp コマンドを「&」で3 個ほぼ同時に実行。)をかけると、
54.516s 換算3.187Gibps
でした。3 Gibps 以上出ていますが、実用には分割と結合の時間が別にかかりますので自動で行ってもPython とどっこいになります。
なおThunderbolt Bridge を3 系列なので接続速度の理論値は10Gbps x 3 です。
下は、コピーしたファイルが、データとして同一で有ることを証明するためmd5 でハッシュを取る部分を追加した動画です。
Gigabit Ethernet の倍以上でていますから、まあ大きなファイルをコピーするには使えるかなと思っています。
シングルのcp コマンドやFinder でのコピーより常に早いですが、
分割して結合の時間が追加で必要になることを含めても、せめて4Gibps 位でても良い気がします。
ソースコードです。ド素人が作製しています。リストの作成方法などはボロボロな気が自分でもしています。
なので、
私の所では正常に動作していますが、人様の環境では確認していません。なお「アトリビュートなどはコピーされません。」
並列実行時、並列で読み出す関数以外が、何故か1 回余分に実行されているらしい異常も確認されています。
またnfs で3 本を独立で繋ぐ必要があります。SMB では異常が出ます。
例えば、
nfs://192.168.2.1/Users/Shared
nfs://192.168.3.1/Users/Shared-1
nfs://192.168.4.1/Users/Shared-2
とコピー先の同一のShared フォルダを別々のIP アドレスでマウントしてます。
下記ソースコードはインデントが崩れているかも知れません。使用時には適宜、訂正を。
作製にお世話になったサイトは下記。他、Python の勉強に多数のサイトを閲覧しました。多すぎてどの際とか不明になりましたが、皆様ありがとうございます。
https://qiita.com/5zm/items/49118188d76e61ca5113
https://qiita.com/yukiB/items/203a6248c2d466b80d38
https://note.nkmk.me/python-list-ndarray-1d-to-2d/
#!/usr/bin/env Python3
import sys
import os
import pathlib
import math
from multiprocessing import Pool
#指定されたファイルの、指定された場所から指定されたサイズで読み出し、指定されたファイルに書き出す関数
#startD 読み出し開始位置、endD 読み出し終了位置、partsizeD 指定された読み出しサイズ、sourceFileD 読み出すファイル、writeFileD 書き出すファイル
def partreadwrite(startD, endD, partsizeD, sourceFileD, writeFileD):
print("writeFileD = ", writeFileD)
y = 0
readPoint = []
readPointsize = []
jan = (endD - startD) / partsizeD
if jan > 1:
#reCo 読み出す回数
reCo = int(jan)
else:
print("想定外")
for y in range(reCo):
readPoint.append(startD + (partsizeD * y))
readPointsize.append(partsizeD)
readPoint.append(startD + (partsizeD * reCo))
endpartsize = endD - readPoint[reCo]
readPointsize.append(endpartsize)
#pp 動作確認用。不要。
pp = "{0} {1:,} {2} {3:,} {4}{5:,}".format("endCalc", endpartsize + readPoint[len(readPoint) - 1], "endpartsize", endpartsize, "jan = ", jan)
with open(sourceFileD, 'rb') as sourceFileD2:
with open(writeFileD, 'r+b') as writeFileD3:
for z in range(reCo + 1):
sourceFileD2.seek(readPoint[z])
data = sourceFileD2.read(readPointsize[z])
writeFileD3.seek(readPoint[z])
writeFileD3.write(data)
#リストをpartreadwrite に渡すためのラッパー。とか言うらしい。
def wrapper_partreadwrite(argsd):
return partreadwrite(*argsd)
#1 次元リストを2 次元にする回数。
def convert_1d_to_2d(l, cols):
return [l[i:i + cols] for i in range(0, len(l), cols)]
#args コマンドラインからの引数。sourceFile コピーしたいファイル、savePathx コピー先のパス、chunkSize このサイズで読み出しを続ける、fileSize ファイルのサイズを取得
args = sys.argv
sourceFile = pathlib.Path(args[1])
savePath = pathlib.Path(args[2])
savePath2 = pathlib.Path(args[3])
savePath3 = pathlib.Path(args[4])
chunkSize = 1500 * pow(10, 5) # 150,000,000
#saveFile = savePath.joinpath(sourceFile.name)
fileSize = os.path.getsize(sourceFile)
i = 0
start = []
fromto = []
end = []
partsize = chunkSize
#parnum 並列処理の数
parnum = 3
saveFile = savePath.joinpath(sourceFile.name)
saveFile2 = savePath2.joinpath(sourceFile.name)
saveFile3 = savePath3.joinpath(sourceFile.name)
#print("saveFile = ", saveFile)
#コピー先にソースファイルと同じサイズのファイルを作成
with open(saveFile, 'wb') as saveFilex:
saveFilex.seek(fileSize - 1, os.SEEK_SET)
# print("saveFilex = ", saveFilex)
saveFilex.write(b'a')
#partreadwrite(startD, endD, partsizeD, sourceFileD, writeFileD) に渡すリストの作成
for i in range(parnum):
start.append(i * int(fileSize / parnum))
end.append(start[i] + int(fileSize / parnum))
end[len(end) - 1] = fileSize
i = 0
for i in range(parnum):
fromto.append(start[i])
fromto.append(end[i])
fromto.append(partsize)
fromto.append(sourceFile)
if i == 0:
fromto.append(saveFile)
# print("i = ", i, "sF = ", saveFile)
elif i== 1:
fromto.append(saveFile2)
# print("i = ", i, "sF = ", saveFile2)
elif i == 2:
fromto.append(saveFile3)
# print("i = ", i, "sF = ", saveFile3)
#作製したリストを二次元化
fromto = convert_1d_to_2d(fromto, 5)
#print("fromto = ", fromto)
#partreadwrite 関数を並列で実行
i = 0
if __name__ == "__main__":
p = Pool(parnum)
p.map(wrapper_partreadwrite, fromto)
p.close()