7

それぞれ長さが2億2000万(固定)のint配列とfloat配列があります。ここで、これらの配列をメモリとディスクに格納/アップロードしたいと思います。現在、これを解決するためにJavaNIOのFileChannelとMappedByteBufferを使用しています。正常に動作しますが、アレイをメモリからディスクに保存/アップロードするのに約5秒(壁掛け時計時間)かかります。今、もっと速くしたいです。

ここで、これらの配列要素のほとんどは0(ほぼ52%)であることに言及する必要があります。

お気に入り:

int arr1 [] = { 0 , 0 , 6 , 7 , 1, 0 , 0 ...}

誰かが私を助けることができますか、それらの0を保存またはロードしないことによって速度を改善するための良い方法はありますか?これは、Arrays.fill(array、0)を使用して補正できます。

4

4 に答える 4

5

次のアプローチでは、ディスク上にn / 8 + nz * 4バイトが必要です。ここで、nは配列のサイズ、nzはゼロ以外のエントリの数です。52%のゼロエントリの場合、ストレージサイズを52%-3%= 49%削減します。

あなたができること:

void write(int[] array) {
    BitSet zeroes = new BitSet();
    for (int i = 0; i < array.length; i++)
        zeroes.set(i, array[i] == 0);
    write(zeroes); // one bit per index
    for (int i = 0; i < array.length; i++)
        if (array[i] != 0)
            write(array[y]);
}

int[] read() {
    BitSet zeroes = readBitSet();
    array = new int[zeroes.length];
    for (int i = 0; i < zeroes.length; i++) {
        if (zeroes.get(i)) {
            // nothing to do (array[i] was initialized to 0)
        } else {
            array[i] = readInt();
        }
    }
}

編集:これが少し遅いと言うことは、ディスクがボトルネックではないことを意味します。上記のアプローチは、ビットセットを作成するときに書き込むことで調整できるため、ディスクに書き込む前にビットセットをメモリに書き込む必要はありません。また、実際のデータを散在させたビットセットを単語ごとに書き込むことにより、配列に対して1回のパスしか実行できず、キャッシュミスが減少します。

void write(int[] array) {
    writeInt(array.length);
    int ni;
    for (int i = 0; i < array.length; i = ni) {
        ni = i + 32;
        int zeroesMap = 0;
        for (j = i + 31; j >= i; j--) {
            zeroesMap <<= 1;
            if (array[j] == 0) {
                zeroesMap |= 1;
            }
        }
        writeInt(zeroesMap);
        for (j = i; j < ni; j++)
            if (array[j] != 0) {
                writeInt(array[j]);
            }
        }
    }
}

int[] read() {
    int[] array = new int[readInt()];
    int ni;
    for (int i = 0; i < array.length; i = ni) {
        ni = i + 32;
        zeroesMap = readInt();
        for (j = i; j < ni; j++) {
            if (zeroesMap & 1 == 1) {
                // nothing to do (array[i] was initialized to 0)
            } else {
                array[j] = readInt();
            }
            zeroesMap >>= 1;
        }
    }
    return array;
}

(前のコードは、array.lengthが32の倍数であることを前提としています。そうでない場合は、配列の最後のスライスを好きなように記述してください)

それでも処理時間が短縮されない場合は、圧縮を行う方法はありません(汎用の圧縮アルゴリズムが上記よりも高速になるとは思いません)。

于 2012-06-28T17:35:08.897 に答える
4

分布に応じて、ランレングスエンコーディングを検討してください。

ランレングスエンコーディング(RLE)は、データの実行(つまり、同じデータ値が多くの連続するデータ要素で発生するシーケンス)が、単一のデータ値およびカウントとしてではなく、単一のデータ値およびカウントとして格納される、非常に単純な形式のデータ圧縮です。元の実行として。これは、そのような実行が多数含まれているデータで最も役立ちます。

それは単純です...これは良いことですが、おそらく悪いことです;-)

于 2012-06-28T17:19:00.430 に答える
2

シリアル化-非正規化コードを自分で作成する場合は、すべてのゼロを保存する代わりに、実際のゼロ以外のデータとともに、それらのゼロがどこにあるかを示す一連の範囲を(特別なマーカーで)保存できます。

したがって、例の配列:{0、0、6、7、1、0、0...}は次のように格納できます。

%0-1、6、7、1%5-6

このデータを読み取るときに、%を押すと、範囲内にあることを意味し、開始と終了を読み取り、ゼロを埋めます。次に、続けて#以外の値が表示されます。これは、実際の値に到達したことを意味します。

連続する値のシーケンスが大きいスパース配列では、これにより大きな圧縮が得られます。

于 2012-06-28T17:26:16.510 に答える
2

javaには標準の圧縮utilsがあります:java.util.zip-これは汎用ライブラリですが、可用性が非常に高いため、問題のないソリューションです。必要に応じて、特殊な圧縮、エンコーディングを調査する必要があります。選択の中心としてzipを使用することはめったにありません。

を介してzipを処理する方法のサンプルを次に示しますDeflater/Inflater。ほとんどの人はZipInput/Output Stream(および特にGzip)を知っています。それらはすべて、mem->zlibおよびespからのコピーの処理に問題があります。CRC32がネイティブコードを呼び出すという完全な災害であるGZip(ネイティブコードを呼び出すと、最適化する機能が削除され、パフォーマンスがさらに低下します)。

重要な注意事項:zip圧縮を高くしないでください。パフォーマンスが低下します。もちろん、CPUとディスクのアクティビティの最適な比率を試して適合させることができます。

java.util.zipこのコードは、直接バッファをサポートしていないという本当の欠点の1つも示しています。サポートは些細なことではありませんが、誰もそれを気にすることはありません。ダイレクトバッファは、メモリコピーをほとんど節約せず、メモリフットプリントを削減します。

最後の注意:(j)zlibのJavaバージョンがあり、ネイティブimplよりも優れています。圧縮に関しては非常にうまくいきます。

package t1;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Random;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

public class ZInt {
    private static final int bucketSize = 1<<17;//in real world should not be const, but we bored horribly
    static final int zipLevel = 2;//feel free to experiement, higher compression (5+)is likely to be total waste
    
    static void write(int[] a, File file, boolean sync) throws IOException{
        byte[] bucket = new byte[Math.min(bucketSize,  Math.max(1<<13, Integer.highestOneBit(a.length >>3)))];//128KB bucket
        byte[] zipOut = new byte[bucket.length];
        
        final FileOutputStream fout = new FileOutputStream(file);
        FileChannel channel = fout.getChannel();
        try{
            
            ByteBuffer buf = ByteBuffer.wrap(bucket);
            //unfortunately java.util.zip doesn't support Direct Buffer - that would be the perfect fit
            ByteBuffer out = ByteBuffer.wrap(zipOut);
            out.putInt(a.length);//write length aka header
            if (a.length==0){
                doWrite(channel, out, 0);
                return;
            }
            
            Deflater deflater = new Deflater(zipLevel, false);
            try{
                for (int i=0;i<a.length;){
                    i = put(a, buf, i);
                    buf.flip();
                    deflater.setInput(bucket, buf.position(), buf.limit());

                    if (i==a.length)
                        deflater.finish();

                    //hacking and using bucket here is tempting since it's copied twice but well
                    for (int n; (n= deflater.deflate(zipOut, out.position(), out.remaining()))>0;){
                        doWrite(channel, out, n);
                    }
                    buf.clear();
                }
                
            }finally{
                deflater.end();
            }
        }finally{
            if (sync) 
                fout.getFD().sync();
            channel.close();
        }
    }

    static int[] read(File file) throws IOException, DataFormatException{
        FileChannel channel = new FileInputStream(file).getChannel();
        try{
            byte[] in = new byte[(int)Math.min(bucketSize, channel.size())];
            ByteBuffer buf = ByteBuffer.wrap(in);

            channel.read(buf);
            buf.flip();
            int[] a = new int[buf.getInt()];
            if (a.length==0)
                return a;
            int i=0;
            byte[] inflated = new byte[Math.min(1<<17, a.length*4)];
            ByteBuffer intBuffer = ByteBuffer.wrap(inflated);
            Inflater inflater = new Inflater(false);
            try{
                do{
                    if (!buf.hasRemaining()){
                        buf.clear();
                        channel.read(buf);
                        buf.flip();
                    }
                    inflater.setInput(in, buf.position(), buf.remaining());
                    buf.position(buf.position()+buf.remaining());//simulate all read

                    for (;;){
                        int n = inflater.inflate(inflated,intBuffer.position(), intBuffer.remaining());
                        if (n==0)
                            break;
                        intBuffer.position(intBuffer.position()+n).flip();
                        for (;intBuffer.remaining()>3 && i<a.length;i++){//need at least 4 bytes to form an int
                            a[i] = intBuffer.getInt();
                        }
                        intBuffer.compact();
                    }

                }while (channel.position()<channel.size() && i<a.length);
            }finally{
                inflater.end();
            }
            //          System.out.printf("read ints: %d - channel.position:%d %n", i, channel.position());
            return a;
        }finally{
            channel.close();
        }
    }

    private static void doWrite(FileChannel channel, ByteBuffer out, int n) throws IOException {
        out.position(out.position()+n).flip();
        while (out.hasRemaining())
            channel.write(out);
        out.clear();
    }
    private static int put(int[] a, ByteBuffer buf, int i) {
        for (;buf.hasRemaining() && i<a.length;){
            buf.putInt(a[i++]);
        }
        return i;
    }
    
    private static int[] generateRandom(int len){
        Random r = new Random(17);
        int[] n = new int[len];
        for (int i=0;i<len;i++){
            n[i]= r.nextBoolean()?0: r.nextInt(1<<23);//limit bounds to have any sensible compression
        }
        return n;
    }
    public static void main(String[] args) throws Throwable{
        File file = new File("xxx.xxx");
        int[] n = generateRandom(3000000); //{0,2,4,1,2,3};
        long start = System.nanoTime();
        write(n, file, false);
        long elapsed = System.nanoTime() - start;//elapsed will be fairer if the sync is true
        
        System.out.printf("File length: %d, for %d ints, ratio %.2f in %.2fms %n", file.length(), n.length, ((double)file.length())/4/n.length, java.math.BigDecimal.valueOf(elapsed, 6) );
        
        int[] m = read(file);
        
        //compare, Arrays.equals doesn't return position, so it sucks/kinda
        for (int i=0; i<n.length; i++){
            if (m[i]!=n[i]){
                System.err.printf("Failed at %d%n",i);
                break;
            }
        }
        System.out.printf("All done!");
    };
    
}

コードは適切なベンチマークではないことに注意してください!
返信の遅れは、コーディングが非常に退屈だったという事実に起因しています。さらに別のzipの例です。申し訳ありません。

于 2012-07-01T12:04:52.700 に答える