8

バイト[]の値を反復する代わりに、メモリを反復するためにUnsafeを試しています。unsafe を使用してメモリ ブロックが割り当てられます。メモリーは、65536 バイトの値を保持するのに十分です。

私はこれを試しています:

char aChar = some character

if ((byte) 0 == (unsafe.getByte(base_address + aChar) & mask)){
 // do something
}

それ以外の:

char aChar = some character

if ((byte) 0 == ( lookup[aChar] & mask )){
 // do something
}

Unsafe は、各インデックスに対して行うインデックス チェックで通常の配列アクセスを使用するよりも高速にメモリにアクセスできると思いました...

jvm に特別な操作 (安全でない) があり、何らかの方法で通常の配列アクセスと反復を高速化するというのは希望的観測に過ぎませんでした。jvm は、通常の byte[] 反復で正常に動作し、通常の純粋なバニラ Java コードを使用して、可能な限り高速に実行するようです。

@millimooseは、ことわざの「頭に釘」を打ちます

「安全でないことは多くのことに役立つかもしれませんが、このレベルのマイクロ最適化はその 1 つではありません。 – ミリムース」

Unsafe を使用すると、非常に厳密に制限された一連の状況で高速になります。

  • (64 ビット jvm のみ)各テストで1 回だけ実行される単一の 65535 byte[] ルックアップのほうが高速です。この場合、64 ビット jvm の UnsafeLookup_8B は 24% 高速です。各テストが 2 回行われるようにテストが繰り返される場合、通常の方法は安全でない方法より 30% 高速になります。コールド jvm の純粋な解釈モードでは、Unsafe の方がはるかに高速です --- ただし、初回のみで、配列サイズが小さい場合のみです。32 ビット標準の Oracle JVM 7.x では、通常は unsafe を使用するよりも 3 倍高速です。

(私のテストでは) Unsafe を使用すると遅くなります。

  • Oracle Java 64 ビットおよび 32 ビット仮想マシンの両方で遅くなる
  • OS やマシン アーキテクチャ (32 ビットおよび 64 ビット) に関係なく遅くなる
  • serverjvm オプションが呼び出されても遅い

  • Unsafe は 9% 以上遅くなります (以下の 32 ビット jvm のコードでは 1_GB 配列と UnsafeLookup_8B(最速のもの) (64 ビットはさらに遅かった??))

  • Unsafe は 234% 以上遅い (1_MB 配列と UnsafeLookup_1B (最速のもの) 以下のコードでは、64 ビット jvm で.

これには何らかの理由がありますか?**

以下に投稿されたコード yellowB を実行すると (1GB バイト [] をチェック)、通常も最速です。

C:\Users\wilf>java -Xms1600m -Xprof -jar "S:\wilf\testing\dist\testing.jar"
initialize data...
initialize data done!

use normalLookup()...
Not found '0'
time : 1967737 us.

use unsafeLookup_1B()...
Not found '0'
time : 2923367 us.

use unsafeLookup_8B()...
Not found '0'
time : 2495663 us.

Flat profile of 26.35 secs (2018 total ticks): main

  Interpreted + native   Method
  0.0%     1  +     0    test.StackOverflow.main
  0.0%     1  +     0    Total interpreted

     Compiled + native   Method
 67.8%  1369  +     0    test.StackOverflow.main
 11.7%   236  +     0    test.StackOverflow.unsafeLookup_8B
 11.2%   227  +     0    test.StackOverflow.unsafeLookup_1B
  9.1%   184  +     0    test.StackOverflow.normalLookup
 99.9%  2016  +     0    Total compiled

         Stub + native   Method
  0.0%     0  +     1    sun.misc.Unsafe.getLong
  0.0%     0  +     1    Total stub


Flat profile of 0.00 secs (1 total ticks): DestroyJavaVM

  Thread-local ticks:
100.0%     1             Blocked (of total)


Global summary of 26.39 seconds:
100.0%  2023             Received ticks


C:\Users\wilf>java -version
java version "1.7.0_07"
Java(TM) SE Runtime Environment (build 1.7.0_07-b11)
Java HotSpot(TM) Client VM (build 23.3-b01, mixed mode, sharing)

CPU: Intel Core 2 Duo E4600 @ 2.4GHZ 4.00GB (3.25GB 使用可能) OS: Windows 7 (32)

Windows 7_64、32 ビット Java を搭載した 4 コア AMD64 でテストを実行します。

initialize data...
initialize data done!

use normalLookup()...
Not found '0'
time : 1631142 us.

use unsafeLookup_1B()...
Not found '0'
time : 2365214 us.

use unsafeLookup_8B()...
Not found '0'
time : 1783320 us.

Windows 7_64、64 ビット Java を搭載した 4 コア AMD64 でテストを実行:

use normalLookup()...
Not found '0'
time : 655146 us.

use unsafeLookup_1B()...
Not found '0'
time : 904783 us.

use unsafeLookup_8B()...
Not found '0'
time : 764427 us.

Flat profile of 6.34 secs (13 total ticks): main

  Interpreted + native   Method
 23.1%     3  +     0    java.io.PrintStream.println
 23.1%     3  +     0    test.StackOverflow.unsafeLookup_8B
 15.4%     2  +     0    test.StackOverflow.main
  7.7%     1  +     0    java.io.DataInputStream.<init>
 69.2%     9  +     0    Total interpreted

     Compiled + native   Method
  7.7%     0  +     1    test.StackOverflow.unsafeLookup_1B
  7.7%     0  +     1    test.StackOverflow.main
  7.7%     0  +     1    test.StackOverflow.normalLookup
  7.7%     0  +     1    test.StackOverflow.unsafeLookup_8B
 30.8%     0  +     4    Total compiled


Flat profile of 0.00 secs (1 total ticks): DestroyJavaVM

  Thread-local ticks:
100.0%     1             Blocked (of total)


Global summary of 6.35 seconds:
100.0%    14             Received ticks
 42.9%     6             Compilation
4

3 に答える 3

5

あなたが投稿した2つの関数は基本的に同じだと思います.1バイトだけを読み込んでintに変換し、さらに比較を行うからです。

毎回 4 バイトの int または 8 バイトの long を読み取る方がはるかに効果的です。同じことを行う 2 つの関数を作成しました。2 つの byte[] の内容を比較して、それらが同じかどうかを確認します。

機能 1:

public static boolean hadoopEquals(byte[] b1, byte[] b2)
  {
    if(b1 == b2)
    {
      return true;
    }
    if(b1.length != b2.length)
    {
      return false;
    }
    // Bring WritableComparator code local

    for(int i = 0;i < b1.length; ++i)
    {
     int a = (b1[i] & 0xff);
     int b = (b2[i] & 0xff);
     if (a != b) 
     {
       return false;
     }
    }
    return true;
  }

機能 2:

public static boolean goodEquals(byte[] b1,byte[] b2)
  {   
    if(b1 == b2)
    {
      return true;
    }
    if(b1.length != b2.length)
    {
      return false;
    }
    int baseOffset = UnSafe.arrayBaseOffset(byte[].class);

    int numLongs = (int)Math.ceil(b1.length / 8.0);

    for(int i = 0;i < numLongs; ++i)
    {
      long currentOffset = baseOffset + (i * 8);
      long l1 = UnSafe.getLong(b1, currentOffset);
      long l2 = UnSafe.getLong(b2, currentOffset);
      if(0L != (l1 ^ l2))
      {
        return false;
      }
    }
    return true;    
  }

これらの 2 つの関数をラップトップ (corei7 2630QM、8GB DDR3、64 ビット win 7、64 ビット Hotspot JVM) で実行し、2 つの 400MB バイト [] を比較しました。結果は次のとおりです。

機能 1: ~670ms

機能 2: ~80ms

2 の方がはるかに高速です。

したがって、毎回 8 バイトを読み取り、XOR 演算子 (^) を使用することをお勧めします。

long l1 = UnSafe.getLong(byteArray, offset);  //8 byte
if(0L == l1 ^ 0xFF)  //if the lowest byte == 0?
/* do something */
if(0L == l1 ^ 0xFF00)  //if the 2nd lowest byte == 0?
/* do something */
/* go on... */

================================================== ==========================

こんにちはウィルフ、あなたのコードを使用して、以下のようなテスト クラスを作成します。このクラスは、バイト配列の最初の 0 を検索する際に 3 つの関数の速度を比較します。

package test;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/**
 * Test the speed in looking up the 1st 0 in a byte array
 * Set -Xms the same as -Xms to avoid Heap reallocation
 * 
 * @author yellowb
 *
 */
public class StackOverflow
{
    public static Unsafe UnSafe;

    public static Unsafe getUnsafe() throws SecurityException,
            NoSuchFieldException, IllegalArgumentException,
            IllegalAccessException
    {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        return unsafe;
    }

    /**
     * use 'byte[index]' form to read 1 byte every time
     * @param buf
     */
    public static void normalLookup(byte[] buf)
    {
        for (int i = 0; i < buf.length; ++i)
        {
            if ((byte) 0 == buf[i])
            {
                System.out.println("The 1st '0' is at position : " + i);
                return;
            }
        }
        System.out.println("Not found '0'");
    }

    /**
     * use Unsafe.getByte to read 1 byte every time directly from the memory
     * @param buf
     */
    public static void unsafeLookup_1B(byte[] buf)
    {
        int baseOffset = UnSafe.arrayBaseOffset(byte[].class);
        for (int i = 0; i < buf.length; ++i)
        {
            byte b = UnSafe.getByte(buf, (long) (baseOffset + i));
            if (0 == ((int) b & 0xFF))
            {
                System.out.println("The 1st '0' is at position : " + i);
                return;
            }

        }
        System.out.println("Not found '0'");
    }

    /**
     * use Unsafe.getLong to read 8 byte every time directly from the memory
     * @param buf
     */
    public static void unsafeLookup_8B(byte[] buf)
    {
        int baseOffset = UnSafe.arrayBaseOffset(byte[].class);

        //The first (numLongs * 8) bytes will be read by Unsafe.getLong in below loop
        int numLongs = buf.length / 8;
        long currentOffset = 0L;
        for (int i = 0; i < numLongs; ++i)
        {
            currentOffset = baseOffset + (i * 8);  //the step is 8 bytes
            long l = UnSafe.getLong(buf, currentOffset);
            //Compare each byte(in the 8-Byte long) to 0
            //PS:x86 cpu is little-endian mode
            if (0L == (l & 0xFF))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8));
                return;
            }
            if (0L == (l & 0xFF00L))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8 + 1));
                return;
            }
            if (0L == (l & 0xFF0000L))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8 + 2));
                return;
            }
            if (0L == (l & 0xFF000000L))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8 + 3));
                return;
            }
            if (0L == (l & 0xFF00000000L))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8 + 4));
                return;
            }
            if (0L == (l & 0xFF0000000000L))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8 + 5));
                return;
            }
            if (0L == (l & 0xFF000000000000L))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8 + 6));
                return;
            }
            if (0L == (l & 0xFF00000000000000L))
            {
                System.out.println("The 1st '0' is at position : " + (i * 8 + 7));
                return;
            }
        }

        //If some rest bytes exists
        int rest = buf.length % 8;
        if(0 != rest)
        {
            currentOffset = currentOffset + 8;
            //Because the length of rest bytes < 8,we have to read them one by one
            for(; currentOffset < (baseOffset + buf.length); ++currentOffset)
            {
                byte b = UnSafe.getByte(buf, (long)currentOffset);
                if (0 == ((int) b & 0xFF))
                {
                    System.out.println("The 1st '0' is at position : " + (currentOffset - baseOffset));
                    return;
                }
            }
        }
        System.out.println("Not found '0'");
    }

    public static void main(String[] args) throws SecurityException,
            NoSuchFieldException, IllegalArgumentException,
            IllegalAccessException
    {
        UnSafe = getUnsafe();

        int len = 1024 * 1024 * 1024;  //1G
        long startTime = 0L;
        long endTime = 0L;

        System.out.println("initialize data...");
        byte[] byteArray1 = new byte[len];
        for (int i = 0; i < len; ++i)
        {
            byteArray1[i] = (byte) (i % 128 + 1);  //No byte will equal to 0
        }
        //If you want to set one byte to 0,uncomment the below statement
//      byteArray1[2500] = (byte)0;
        System.out.println("initialize data done!");

        System.out.println("use normalLookup()...");
        startTime = System.nanoTime();
        normalLookup(byteArray1);
        endTime = System.nanoTime();
        System.out.println("time : " + ((endTime - startTime) / 1000) + " us.");

        System.out.println("use unsafeLookup_1B()...");
        startTime = System.nanoTime();
        unsafeLookup_1B(byteArray1);
        endTime = System.nanoTime();
        System.out.println("time : " + ((endTime - startTime) / 1000) + " us.");

        System.out.println("use unsafeLookup_8B()...");
        startTime = System.nanoTime();
        unsafeLookup_8B(byteArray1);
        endTime = System.nanoTime();
        System.out.println("time : " + ((endTime - startTime) / 1000) + " us.");
    }
}

出力は次のとおりです。

initialize data...
initialize data done!
use normalLookup()...
Not found '0'
time : 1271781 us.
use unsafeLookup_1B()...
Not found '0'
time : 716898 us.
use unsafeLookup_8B()...
Not found '0'
time : 591689 us.

結果は、Unsafe.getByte() によって毎回 1 バイトを読み取ることでさえ、定期的に byte[] を反復するよりもはるかに高速であることを示しています。

于 2012-09-19T04:50:06.157 に答える
1

Unsafeは、各インデックスに対して行うインデックスチェックで通常の配列アクセスを使用するよりも速くメモリにアクセスできると思いました...

範囲チェックが要因にならない理由の1つは、JITコンパイラのオプティマイザです。配列のサイズは変更されないため、オプティマイザがすべての範囲チェックを「巻き上げ」、ループの開始時に1回実行することが可能になる場合があります。

対照的に、JITコンパイラはUnsafe.getByte()呼び出しを最適化(インラインなど)できない場合があります。または、getByteメソッドに読み取りバリアがあるかもしれません...)

しかし、これは憶測です。確実にする方法は、JVMに2つのケースのJITコンパイルされたネイティブコードをダンプさせ、それらを命令ごとに比較することです。

于 2012-09-21T02:01:37.413 に答える
0

安全でないメソッドはネイティブとしてマークされる場合がありますが、必ずしも JNI であるとは限りません。ほとんどすべての Unsafe メソッドは組み込み関数です (ここの短い投稿を参照してください: http://psy-lob-saw.blogspot.co.uk/2012/10/java-intrinsics-are-not-jni-calls.html )。 Sun JVM それらは (多くの場合) 単一のアセンブリ命令に変換されます。他の JVM では、組み込み関数の処理が得意である場合とそうでない場合があり、JNI 呼び出しまたはプレーンな Java 呼び出しに変換される場合があります。私が知っている限りでは、JRockit は JNI の方向に進む傾向があり、Android JVM も同様です。

于 2012-12-25T13:39:17.057 に答える