많은 프로젝트가 있을 때 굳이 pom.xml에 기술하는 것보다 하나의 파일로 관리하는 게 편할 수 있다.
 profile, mirror를 공통관리함으로서 pom.xml 파일을 적당히 관리한다.

${user.home}/.m2/settings.xml


Posted by '김용환'
,

통신, 파일과 관련해서 read 할때, 쓰레기값도 같이 읽을 수 있기 때문에 memset은 기본적으로 하는 것이 좋다. 나중에 문제가 커지지 않는 안티 버그 습관이랄까..

open jdk의 소스 패치를 보면서, 얘네들도 실수해서 코딩하기도 하는구나.. 하는 것을 알게 된다.


http://cr.openjdk.java.net/~zhangshj/7152948/webrev.00/ 
  {
      jint fd = fdval(env, fdo);
      ssize_t result = 0;
      struct iovec *iov = (struct iovec *)jlong_to_ptr(address);
      struct msghdr m;
+     // initialize the message
+     memset(&m, 0, sizeof(m));
      if (len > 16) {
          len = 16;
      }
  
      m.msg_iov = iov;
      m.msg_iovlen = len;
  
      result = recvmsg(fd, &m, 0);
      if (result < 0 && errno == ECONNREFUSED) {
          JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException", 0);
          return -2;


 

'c or linux' 카테고리의 다른 글

리눅스 메모리 관련 파라미터  (0) 2012.04.02
iproute2 (tc)  (0) 2012.03.12
라인 피드 문제  (0) 2012.03.08
리눅스 및 MMU 가상메모리 공부  (1) 2012.02.22
리눅스에서 디스크 용량 체크  (0) 2012.02.07
Posted by '김용환'
,

 

아주 훌륭하신 분들이 스타트업 바이블 이북을 공짜로 올리셨다.

2주안만 무료로 다운받을 수 있도록 해주신다니  받으면 좋을 것 같다. iOS의 iBook과도 호환된다.

http://www.baenefit.com/2012/03/ibook.html

 

image

image

Posted by '김용환'
,

 

Hudson을 이용해서 Spring Batch Job을 동작시킬 때 Job Parameter를 쉽게 넘길 수 있는 방법이 있다.

 

Hudson 설정에서 This build is parameterized 체크박스를 on으로 하고, parameter값을 추가한다.

image

 

Hudson에서 실행할 때 (build now), 아래와 같은 화면이 나온다.

image

 

실행 쉘에 파라미터를 넣어주도록 하고 넘기면..

image

 

잘 넘어가서 실행된다.

image

'scribbling' 카테고리의 다른 글

maven의 settings.xml 파일 위치  (0) 2012.03.12
스타트업 바이블 iBook(무료배포)  (0) 2012.03.09
Anatomy of the Google Architecture  (0) 2012.02.23
Google File System 자료  (0) 2012.02.23
Cut the rope 웹 브라우져에서 하기  (0) 2012.02.22
Posted by '김용환'
,

라인 피드 문제

c or linux 2012. 3. 8. 19:09

.bashrc가 문제가 없는데, 계속 .bashrc를 못읽는다면. 파일 포맷을 의심한다.

-bash-3.00$ source .bashrc_backup
-bash: .bashrc_backup: line 7: syntax error: unexpected end of file


윈도우에서 커밋한 라인피드가 문제이다.
 
-bash-3.00$ file /home/www/work/.bashrc
/home/www/work/.bashrc: ASCII text, with CRLF line terminators



-bash-3.00$ perl -pi -e "s/\r//g" .bashrc
-bash-3.00$ file .bashrc
.bashrc: ASCII text
-bash-3.00$ source .bashrc


file /home/www/work/.bashrc
/home/www/work/.bashrc: ASCII text

잘 동작한다.

'c or linux' 카테고리의 다른 글

iproute2 (tc)  (0) 2012.03.12
struct 초기화 - memset  (0) 2012.03.12
리눅스 및 MMU 가상메모리 공부  (1) 2012.02.22
리눅스에서 디스크 용량 체크  (0) 2012.02.07
awk를 이해하는 데 도움이 되는 글들  (0) 2012.02.03
Posted by '김용환'
,

Team Viewer

Tool 2012. 3. 8. 16:36

 

원격 제어 데스크탑 솔루션으로 가장 좋은 것 같다.

노트북/아이폰/아이패드/리눅스에서 pc로 테스트했을 떄 잘 연결된다. 회의 / 팀용으로도 된다..

이렇게 좋은 툴을 모르고 있었다니.. 흑! 이제부터 잘써야지.

 

아래 글을 참조해서 쉽게 사용할 수 있다.

http://it2011.tistory.com/entry/Teamviewer-%ED%8C%80%EB%B7%B0%EC%96%B4-%EB%8B%A4%EC%9A%B4-%EB%B0%8F%EC%84%A4%EC%B9%98-%EC%86%90%EC%89%AC%EC%9A%B4-%EC%82%AC%EC%9A%A9%EB%B2%95

Posted by '김용환'
,

 

FileDescriptor의 native 메소드가 어떻게 리눅스와 윈도우에 포팅되었는지 확인한다.

 

1. 자바 소스

java.io.FileDescriptor.java

package java.io;

public final class FileDescriptor {

private int fd;

private long handle;

…..

public native void sync() throws SyncFailedException;

/* This routine initializes JNI field offsets for the class */
private static native void initIDs();

private static native long set(int d);

}

 

2, native 소스

jdk 6u 22  소스 - FileDescriptor_md.c

/*
* @(#)FileDescriptor_md.c 1.4 10/03/23
*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/

#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
#include "io_util.h"
#include "jlong.h"
#include "io_util_md.h"

#include "java_io_FileDescriptor.h"

/*******************************************************************/
/* BEGIN JNI ********* BEGIN JNI *********** BEGIN JNI ************/
/*******************************************************************/

/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;

/* field id for jlong 'handle' in java.io.FileDescriptor */
jfieldID IO_handle_fdID;

/**************************************************************
* static methods to store field IDs in initializers
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
    IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
    IO_handle_fdID = (*env)->GetFieldID(env, fdClass, "handle", "J");
}

JNIEXPORT jlong JNICALL
Java_java_io_FileDescriptor_set(JNIEnv *env, jclass fdClass, jint fd) {
    SET_HANDLE(fd);
}

/**************************************************************
* File Descriptor
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_sync(JNIEnv *env, jobject this) {
    FD fd = THIS_FD(this);
    if (IO_Sync(fd) == -1) {
        JNU_ThrowByName(env, "java/io/SyncFailedException", "sync failed");
    }
}

 

3. 윈도우 구현

윈도우에서는 SET_HANDLE과 IO_Sync을 다음과 같이 정의하였다.

src/windows/native/java/io/io_util_md.h 파일

SET_HANDLE 매크로에서 input,output,error에 값에 대한 GetStdHandle 함수를 사용한다. (http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231(v=vs.85).aspx

JNIEXPORT int handleSync(jlong fd);

/*
* Setting the handle field in Java_java_io_FileDescriptor_set for
* standard handles stdIn, stdOut, stdErr
*/
#define SET_HANDLE(fd) \
if (fd == 0) { \
return (jlong)GetStdHandle(STD_INPUT_HANDLE); \
} else if (fd == 1) { \
return (jlong)GetStdHandle(STD_OUTPUT_HANDLE); \
} else if (fd == 2) { \
return (jlong)GetStdHandle(STD_ERROR_HANDLE); \
} else { \
return (jlong)-1; \
} \

#define IO_Sync handleSync


src/windows/native/java/io/io_util_md.c 파일

결국 자바의 sync는 윈도우의 FlushFileBuffers(핸들값) 함수를 이용한다.  (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364439(v=vs.85).aspx)

JNIEXPORT int
handleSync(jlong fd) {
/*
* From the documentation:
*
* On Windows NT, the function FlushFileBuffers fails if hFile
* is a handle to console output. That is because console
* output is not buffered. The function returns FALSE, and
* GetLastError returns ERROR_INVALID_HANDLE.
*
* On the other hand, on Win95, it returns without error. I cannot
* assume that 0, 1, and 2 are console, because if someone closes
* System.out and then opens a file, they might get file descriptor
* 1. An error on *that* version of 1 should be reported, whereas
* an error on System.out (which was the original 1) should be
* ignored. So I use isatty() to ensure that such an error was due
* to this bogosity, and if it was, I ignore the error.
*/

HANDLE handle = (HANDLE)fd;

if (!FlushFileBuffers(handle)) {
if (GetLastError() != ERROR_ACCESS_DENIED) { /* from winerror.h */
return -1;
}
}
return 0;
}

 

4. 리눅스 구현

src/sloaris/native/java/io/FileDescriptor_md.c

/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;

/**************************************************************
* static methods to store field ID's in initializers
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
    IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
}

/**************************************************************
* File Descriptor
*/

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_sync(JNIEnv *env, jobject this) {
    int fd = (*env)->GetIntField(env, this, IO_fd_fdID);
    if (JVM_Sync(fd) == -1) {
    JNU_ThrowByName(env, "java/io/SyncFailedException", "sync failed");
    }   
}

 

src/solaris/native/java/io/io_util_md.h 파일

set_handle은 깔끔히 무시해준다.

/*
* Route the routines through HPI
*/

#define IO_Sync JVM_Sync

/*
* On Solaris, the handle field is unused
*/
#define SET_HANDLE(fd) return (jlong)-1

 

hotspot/src/share/vm/prims/jvm.cpp 파일

JVM_LEAF(jint, JVM_Sync(jint fd))
    JVMWrapper2("JVM_Sync (0x%x)", fd);
    //%note jvm_r6
    return hpi::fsync(fd);
JVM_END

 

JVMWrapper2 매크로는 jvm.cpp에 정의되어 있다. 특별한 일은 하지 않는다.

#define JVMWrapper2(arg1, arg2) JVMCountWrapper(arg1); JVMTraceWrapper(arg1, arg2)

#define JVMCountWrapper(arg) \
static JVMHistogramElement* e = new JVMHistogramElement(arg); \
if (e != NULL) e->increment_count(); // Due to bug in VC++, we need a NULL check here eventhough it should never happen!

class JVMTraceWrapper : public StackObj {
public:
JVMTraceWrapper(const char* format, ...) {
   if (TraceJVMCalls) {
        va_list ap;
        va_start(ap, format);
        tty->print("JVM ");
        tty->vprint_cr(format, ap);
        va_end(ap);
    }
}
};


hpi::fsync() 함수의 원형은 hotspot/src/share/vm/runtime/hpi.hpp, hotspot/src/share/vm/runtime/hpi.h 에 있다.

hotspot/src/share/vm/runtime/hpi.h

typedef struct {
  char *         (*NativePath)(char *path);
  int            (*FileType)(const char *path);
  int            (*Open)(const char *name, int openMode, int filePerm);
  int            (*Close)(int fd);
  jlong          (*Seek)(int fd, jlong offset, int whence);
  int            (*SetLength)(int fd, jlong length);
  int            (*Sync)(int fd);
  int            (*Available)(int fd, jlong *bytes);
  size_t         (*Read)(int fd, void *buf, unsigned int nBytes);
  size_t         (*Write)(int fd, const void *buf, unsigned int nBytes);
  int            (*FileSizeFD)(int fd, jlong *size);
} HPI_FileInterface;

 

hotspot/src/share/vm/runtime/hpi.cpp

//
// C++ wrapper to HPI.
//

class hpi : AllStatic {

static HPI_FileInterface* _file;

static inline int fsync(int fd);

HPIDECL(fsync, "fsync", _file, Sync, int, "%d",
(int fd),
("fd = %d", fd),
(fd));

}


#define HPIDECL(name, names, intf, func, ret_type, ret_fmt, arg_type, arg_print, arg) \
inline ret_type hpi::name arg_type { \
if (TraceHPI) { \
tty->print("hpi::" names "("); \
tty->print arg_print ; \
tty->print(") = "); \
} \
ret_type result = (*intf->func) arg ; \
if (TraceHPI) { \
tty->print_cr(ret_fmt, result); \
} \
return result; \
}

 

hpi.c

static HPI_FileInterface hpi_file_interface = {
sysNativePath,
sysFileType,
sysOpen,
sysClose,
sysSeek,
sysSetLength,
sysSync,
sysAvailable,
sysRead,
sysWrite,
sysFileSizeFD
};

 

hpi_impl.h

int sysSync(int fd);

 

sys_api_td.c

int
sysSync(int fd) {
/*
* XXX: Is fsync() interruptible by the interrupt method?
* Is so, add the TSD, sigsetjmp()/longjmp() code here.
*
* This probably shouldn't be throwing an error and should
* be a macro.
*/
int ret;
if ((ret = fsync(fd)) == -1) {
}
return ret;
}

 

리눅스의 sync 호출은 간단히 리눅스의 fsync 함수를 호출하는 효과와 동일하다.

 

마치며.

재미있는 것은 파일(file), 소켓(socket), 모니터(lock) 모두 jvm.cpp를 거쳐 hpi.hpp 로 내려오게 된다. 파일의 경우는 IO lock/unlock의 개념도 있고. 공부할 내용이 풍부하다..

이제 시간되는 대로 jvm 내용을 찾아 들어가면 될 것 같다.

Posted by '김용환'
,

 

블룸필터의 의미와 카산드라에서 블룸필터를 어떻게 구현했는지 체크한다.

 

1. 블룸 필터 (Bloom Filter)

블룸 필터는 data 파일의 키값을 모아, 주어진 정보가 있는지 없는지 알려주는 확률 기반의 필터이다. 블룸필터 를 통해서 data가 있는지 빨리 확인할 수 있다. 상식선에서 잘 생각했을 때, 데이터가 없는데 있다는 것는 괜찮지만,  데이터가 있다고 해놓고서는 없는 것은 없다 라는 개념으로 보면 된다. 전자는 false positives, 후자는 false negatives 이다. 아래 내용이 바로 그런 예라고 보면 된다.
(false positives : 참이 아닌데, 실제로는 참이다,  false negatives : 참인데, 실제로는 거짓이다.)

image

(http://en.wikipedia.org/wiki/Bloom_filter 참조)

블룸 필터를 사용하면 일종의 키가 있는지 없는지에 대한 빠른 확인이 가능하다. (전문용어로 to save IO when performing a key lookup) 이렇게 함으로서 disk access를 조금이나다 적게 해서 속도를 높일 수 있다. 캐쉬는 아니지만, 캐쉬 처럼 비슷한 개념이 있다고 말할 수 있다.

블룸 필터는 두가지를 제공한다. 하나는 추가하는 add(), 하나는 존재하는지에 대한 isExist() 이다.  add() 할 때, key를 hash 알고리즘을 이용해서 여러 개의 hash 값을 나누고, 버킷이라는 저장장소에 저장한다. 그래서 isExist할때 그 버킷를 활용해서 있는지를 확인한다. 확률상 없는데, 있다고 나오지 않도록 적절하게 잡는 알고리즘이 최상의 알고리즘이라 할 수 있을 것 이다.

image

(http://en.wikipedia.org/wiki/Bloom_filter 참조)

 

자세한 내용은 아래 내용을 참조하면 쉽게 이해할 수 있다.

Programming Game Gen 의 블룸필터 챕터
http://mindori.egloos.com/1699170
http://blog.naver.com/PostView.nhn?blogId=cra2yboy&logNo=90122287288

 

 

2. 카산드라의 실제 소스 확인

 

카산드라 1.0.5 에서 bloom filter 를 사용하는 곳 (모두 org.apache.cassandra.io.sstable 패키지)은 다음과 같다.

- SSTable 생성시 (Index 용으로 활용) (SSTableWriter의 inner class IndexWriter)

public final BloomFilter bf;
bf = BloomFilter.getFilter(keyCount, 15);

- SSTable을 읽을 때 (SSTableReader)

private Filter bf;
bf = LegacyBloomFilter.getFilter(estimatedKeys, 15);

false positive 확률을 파라미터는 15 정도로 해서 잘 넘기고 있다.

BloomFilter의 getFilter() 메서드의 원형은 다음과 같다.

/**
* @return The smallest BloomFilter that can provide the given false positive
* probability rate for the given number of elements.
*
* Asserts that the given probability can be satisfied using this filter.
*/
public static BloomFilter getFilter(long numElements, double maxFalsePosProbability)
{
    assert maxFalsePosProbability <= 1.0 : "Invalid probability";
    int bucketsPerElement = BloomCalculations.maxBucketsPerElement(numElements);
    BloomCalculations.BloomSpecification spec = BloomCalculations.computeBloomSpec(bucketsPerElement, maxFalsePosProbability);
    return new BloomFilter(spec.K, bucketsFor(numElements, spec.bucketsPerElement));
}

 

# 저장

Filter.db 파일에 블룸필터를 저장한다.  (Componen , SSTable 클래스 참조)

bloom filter를 저장할 때는 serialization하게 저장하게 되어 있다. (org.apache.cassandra.utils.BloomFilterSerializer.java)

bloom filter 의 hashcount 를 integer type으로 저장하고 그 다음에는 워드 개수(word number, bit 길이)를 저장한다. 그 다음에 page size만큼 page를 읽어와 bit 값을 저장한다.

public class BloomFilterSerializer implements ISerializer<BloomFilter>
{
public void serialize(BloomFilter bf, DataOutput dos) throws IOException
{
int bitLength = bf.bitset.getNumWords();
int pageSize = bf.bitset.getPageSize();
int pageCount = bf.bitset.getPageCount();

dos.writeInt(bf.getHashCount());
dos.writeInt(bitLength);

for (int p = 0; p < pageCount; p++)
{
long[] bits = bf.bitset.getPage(p);
for (int i = 0; i < pageSize && bitLength-- > 0; i++)
dos.writeLong(bits[i]);
}
}

}

 

 

# Filter

Bloom Filter는 org.apache.cassandra.utils.Filter 추상 클래스를 상속받았다.

package org.apache.cassandra.utils;

public abstract class Filter
{
int hashCount;

int getHashCount()
{
return hashCount;
}

public abstract void add(ByteBuffer key);

public abstract boolean isPresent(ByteBuffer key);
}

 

# BloomFilter

org.apache.cassandra.utils.BloomFilter.java 이다.

abstract 메서드를 상속받은 두개의 메소드의 구현은 아래와 같다. 
제일 중요한 것은 확률적으로 잘 분산시킬 수 있는 알고리즘인데, 일반적으로는 SHA 기반의 알고리즘을 사용하여 난수를 만들어내는 데, 카산드라는 “Less Hashing, Same Performance: Building a Better Bloom Filter” (http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf) 과 MurMurHash(http://murmurhash.googlepages.com/)를 이용하고 있다. 

SHA보다 빠르고, 적당하게 잘 분산시킬 수(good collision resistance)  있다. hashcount와 주어진  key, OpenBit 길이(디폴트는 20개)로 만들어진 세트(hash bucket)에서 long [] 타입의 정보를 읽는다.

add 일  경우에는 fastGet를

public void add(ByteBuffer key)
{
    for (long bucketIndex : getHashBuckets(key))
    {
    bitset.fastSet(bucketIndex);
    }
}

public boolean isPresent(ByteBuffer key)
{
    for (long bucketIndex : getHashBuckets(key))
    {
        if (!bitset.fastGet(bucketIndex))
        {
            return false;
        }
    }
     return true;
}

private long[] getHashBuckets(ByteBuffer key)
{
    return BloomFilter.getHashBuckets(key, hashCount, bitset.size());
}

    // Murmur is faster than an SHA-based approach and provides as-good collision
    // resistance.  The combinatorial generation approach described in
    // http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
    // does prove to work in actual tests, and is obviously faster
    // than performing further iterations of murmur.
    static long[] getHashBuckets(ByteBuffer b, int hashCount, long max)
    {
        long[] result = new long[hashCount];
        long hash1 = MurmurHash.hash64(b, b.position(), b.remaining(), 0L);
        long hash2 = MurmurHash.hash64(b, b.position(), b.remaining(), hash1);
        for (int i = 0; i < hashCount; ++i)
        {
            result[i] = Math.abs((hash1 + (long)i * hash2) % max);
        }
        return result;
    }


/** Sets the bit at the specified index.
* The index should be less than the OpenBitSet size.
*/
public void fastSet(long index) {
   int wordNum = (int)(index >> 6);
   int bit = (int)index & 0x3f;
   long bitmask = 1L << bit;
   bits[ wordNum / PAGE_SIZE ][ wordNum % PAGE_SIZE ] |= bitmask;
}


/** Returns true or false for the specified bit index.
  * The index should be less than the OpenBitSet size.
  */
public boolean fastGet(long index) {
   int i = (int)(index >> 6);               // div 64
   int bit = (int)index & 0x3f;           // mod 64
   long bitmask = 1L << bit;
   // TODO perfectionist one can implement this using bit operations
   return (bits[i / PAGE_SIZE][i % PAGE_SIZE ] & bitmask) != 0;
}

 

MurmurHash 클래스를 살펴본다. 이 클래스를 살펴보기 전에 성능을 봐야 하는데. intel core 2 duo 2.4 서버를 기준으로 했을 때, 초당 처리율을 최고를 자랑한다. 

Excellent performance - measured on an Intel Core 2 Duo @ 2.4 ghz

OneAtATime - 354.163715 mb/sec
FNV - 443.668038 mb/sec
SuperFastHash - 985.335173 mb/sec
lookup3 - 988.080652 mb/sec
MurmurHash 1.0 - 1363.293480 mb/sec
MurmurHash 2.0 - 2056.885653 mb/sec

 

/**
* This is a very fast, non-cryptographic hash suitable for general hash-based
* lookup. See http://murmurhash.googlepages.com/ for more details.
*
* <p>
* The C version of MurmurHash 2.0 found at that site was ported to Java by
* Andrzej Bialecki (ab at getopt org).
* </p>
*/
public class MurmurHash {


    public static long hash64(ByteBuffer key, int offset, int length, long seed)
    {
        long m64 = 0xc6a4a7935bd1e995L;
        int r64 = 47;

        long h64 = (seed & 0xffffffffL) ^ (m64 * length);

        int lenLongs = length >> 3;

        for (int i = 0; i < lenLongs; ++i)
        {
            int i_8 = i << 3;

            long k64 =  ((long)  key.get(offset+i_8+0) & 0xff)      + (((long) key.get(offset+i_8+1) & 0xff)<<8)  +
                        (((long) key.get(offset+i_8+2) & 0xff)<<16) + (((long) key.get(offset+i_8+3) & 0xff)<<24) +
                        (((long) key.get(offset+i_8+4) & 0xff)<<32) + (((long) key.get(offset+i_8+5) & 0xff)<<40) +
                        (((long) key.get(offset+i_8+6) & 0xff)<<48) + (((long) key.get(offset+i_8+7) & 0xff)<<56);
          
            k64 *= m64;
            k64 ^= k64 >>> r64;
            k64 *= m64;

            h64 ^= k64;
            h64 *= m64;
        }

        int rem = length & 0x7;

        switch (rem)
        {
        case 0:
            break;
        case 7:
            h64 ^= (long) key.get(offset + length - rem + 6) << 48;
        case 6:
            h64 ^= (long) key.get(offset + length - rem + 5) << 40;
        case 5:
            h64 ^= (long) key.get(offset + length - rem + 4) << 32;
        case 4:
            h64 ^= (long) key.get(offset + length - rem + 3) << 24;
        case 3:
            h64 ^= (long) key.get(offset + length - rem + 2) << 16;
        case 2:
            h64 ^= (long) key.get(offset + length - rem + 1) << 8;
        case 1:
            h64 ^= (long) key.get(offset + length - rem);
            h64 *= m64;
        }

        h64 ^= h64 >>> r64;
        h64 *= m64;
        h64 ^= h64 >>> r64;

        return h64;
    }


}

Posted by '김용환'
,