2014-07-20 2 views
0

각 레이어는 프리미티브 (int 또는 int)의 2D 배열 인 여러 가지 레이어에서 상당히 간단한 함수 집합 (평균, 가중 평균/흐림, n 공백 아래로 이동 등)을 실행해야합니다. 흙손). 입력 값은 최대 8k x 4k이지만 더 자주 2k x 1k 미만입니다. 입력 데이터는 변경 불가능하며 모든 셀을 처리 한 후에 출력으로 대체되고 모든 것이 다시 실행됩니다. 실시간으로 실행될 것으로 기대하지는 않지만 초당 약 10 개의 이미지를 처리해야합니다. 모든 입력 함수는 여러 개의 셀을 샘플링 할 수 있지만 단 하나의 셀에만 쓰고 상태는 유지되지 않습니다 (모든 의도와 목적을 위해 프래그먼트 셰이더).서버 측 이미지 처리

클라이언트 측에서이 작업을 수행하면이 작업을 GPU에 맡기고 초당 수천 번의 반복 작업에도 문제가 없습니다. 그러나 이것은 GPU가없는 서버 (아마 VM)에서 실행해야합니다. 서버는 대개 RH 기반의 Linux가 될 것입니다.하지만이 경우 C 라이브러리를 추가하지 않아도됩니다.

코드가 this gist 인 현재 테스트는 4k x 2k 이미지에서 단일 스레드 인 경우 초당 약 8 개의 이미지가 최대 속도가됩니다. 그것은 수용 가능한 성능을위한 최소한의 것입니다, 그리고 멀티 스레딩은 받아 들일 수 있습니다 (여전히 그것을 테스트합니다). JMH는 제공 : 기본 기능으로

Benchmark       Mode Samples  Score Score error Units 
t.ShaderFunc.testProcess   thrpt  40  5.506  0.478 ops/s 
t.ShaderFunc.testProcessInline thrpt  40  7.657  0.561 ops/s 
t.ShaderFunc.testProcessProc  thrpt  40  5.685  0.350 ops/s 

을 :

나를 걱정이 내가 처리됩니다 간단한 기능 중 하나로서
public int blur(final int[][] data, final int x, final int y) { 
    float accumulator = 0; 
    int[] col = data[x]; 
    accumulator += col[y]; 
    if (x > 0) { 
     accumulator += data[x-1][y] * 0.5f; 
    } 
    if (x < data.length - 1) { 
     accumulator += data[x+1][y] * 0.5f; 
    } 
    if (y > 0) { 
     accumulator += col[y-1] * 0.5f; 
    } 
    if (y < col.length - 1) { 
     accumulator += col[y+1] * 0.5f; 
    } 
    return Math.round(accumulator/3f); 
} 

.

처리 기능이 인터페이스 (Java 8, 잠재적으로 람다) 일 수 있지만 처리 기능이 루프에서 인라인 인 경우 일부 초기 벤치 마크는 대략 30 %의 성능 증가를 나타낼 것을 선호합니다. 루프가 얼마나 단단한 지 감안할 때, 호출 오버 헤드가 문제라고 생각할 수 있습니다.

성능을 크게 변경시키는 라이브러리, 내장 또는 기타 기술이 있습니까? 이 코드를 벡터화하는 데 사용할 수있는 기법이 있습니까 (함수가 작동하는 방식을 고려할 때 확실하지는 않습니다).

업데이트 : Stuart Marks의 변경 사항을 통합하고 Aleksey Shipilev의 제안과 함께 실행 한 결과는 매우 달랐습니다. 이것은 내 작업 기계 (VMware Workstation 10의 CentOS 6.5 VM에서 i7-3840QM이있는 Lenovo W530)와 내 가정용 컴퓨터 (Win 8.1의 Surface Pro 2)에 대한 것입니다. 이 잘 내가 기대 한 것 이상으로, 훌륭한 성능이다

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderFunc.testProcess   thrpt  40  40.890  5.772 ops/s 
c.s.q.ShaderFunc.testProcessInline thrpt  40  44.032  2.389 ops/s 
c.s.q.ShaderFunc.testProcessProc  thrpt  40  44.378  2.153 ops/s 

: 제안 된 변경

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderFunc.testProcess   thrpt  40  9.098  0.054 ops/s 
c.s.q.ShaderFunc.testProcessInline thrpt  40  11.337  1.603 ops/s 
c.s.q.ShaderFunc.testProcessProc  thrpt  40  11.706  0.105 ops/s 

(code here) : 나는 업데이 트 (11)

원래 취지 결과를 JDK 1.8를 사용하고 있습니다 .

업데이트 2 : 원하는 코드와 좀 더 가깝게 코드를 변경하면 람다 또는 클래스를 호출하는 오버 헤드가 반환 된 것 같습니다. 코드가 변경되어 클린 상태가되고, 벤치 마크를 some JMH recommendations을 기준으로 리팩토링했습니다.this gist의 코드를 기반으로, 결과는 다음과 같습니다

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderBench.testProcessInline thrpt  200  40.685  0.224 ops/s 
c.s.q.ShaderBench.testProcessLambda thrpt  200  16.077  0.113 ops/s 
c.s.q.ShaderBench.testProcessProc  thrpt  200  23.827  0.088 ops/s 

을 가장 저렴한 디지털 오션 노드에서 :

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderBench.testProcessInline thrpt  200  24.425  0.506 ops/s 
c.s.q.ShaderBench.testProcessLambda thrpt  200  9.643  0.140 ops/s 
c.s.q.ShaderBench.testProcessProc  thrpt  200  13.733  0.134 ops/s 
서버 유형의 VM에
Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderBench.testProcessInline thrpt  200  40.860  0.184 ops/s 
c.s.q.ShaderBench.testProcessLambda thrpt  200  22.603  0.159 ops/s 
c.s.q.ShaderBench.testProcessProc  thrpt  200  22.792  0.117 ops/s 

내가 사용할 계획

허용되는 모든 성능 (제 작업 기계 및 전용 VM에서 꽤 환상적인 성능). 나는 그 전화가 왜 그렇게 큰 오버 헤드를 가지며 그것을 최적화하기 위해 할 수 있는지 알아내는 것에 관심이있다. 현재 다른 매개 변수 집합을 실험하고 a more specific question 게시했습니다.

+0

멀티 스레딩은 성능을 향상시킬 수 있습니다. – vandale

+0

@vandale 거의 확실합니다. 최소한 각 이미지는 별도의 스레드에서 실행될 수 있습니다. 나는 이미지 처리 라이브러리의 방향을 좀 더 찾고있다. 코드를 유행에 따라 벡터화하거나, 병렬 확장을하거나, 그 라인을 따라 무엇인가를 찾고있다. – ssube

답변

2

요점을 제공 해주셔서 감사합니다. 그것은 성능 효과를보기 위해 코드를 둘러보기가 아주 쉬워졌습니다. 또한, JMH 사용에 +1.

첫째, 여기 내 시스템의 기본 결과 (2009 맥북 프로, 2.8GHz의 Core2Duo, JDK의 8u5)입니다

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderFunc.testProcess   thrpt   5  7.191  1.140 ops/s 
c.s.q.ShaderFunc.testProcessInline thrpt   5  7.592  0.465 ops/s 
c.s.q.ShaderFunc.testProcessProc  thrpt   5  7.326  1.242 ops/s 

(CSQ가 com.stackoverflow.questions이다)의 기술 사이의

의 차이는 내 오류는 다소 높지만 인라인 버전은 여전히 ​​빠릅니다. 결과가 너무 가까웠 기 때문에 testProcess을 최적화하기 시작했습니다.이 코드는 blur 메서드에 대한 직접 호출입니다. 여기에 포함 된 코드가 있습니다. 다른 독자의 편의를 위해 blur 방법을 호출하는 코드는 이것이다 :

int width = 4000; 
int height = 2000; 
int[][] nextData = new int[width][height]; 
for (int i = 0; i < width; ++i) { 
    for (int j = 0; j < height; ++j) { 
     nextData[i][j] = blur(blurData, i, j); 
    } 
} 

나의 첫 번째 관찰은 행렬의 가장자리를 스테핑 피하려면 blur 방법 조건문이 많이 있다는 것입니다. 편리하게도, 가장자리에서 수행 된 누적은 "가장자리에서 벗어남"값이 0이면 동일한 결과를 갖습니다 (대부분의 이미지 처리 커널에서 그렇다고 생각합니다). 즉, 행렬의 가장자리를 0으로 채우고 제한을 0 대신 1에서 1까지 루프를 실행하면 조건을 삭제할 수 있습니다. 이것에 루프 변경 :

:

int width = 4002; 
int height = 2002; 
int[][] nextData = new int[width][height]; 
for (int i = 1; i < width-1; ++i) { 
    for (int j = 1; j < height-1; ++j) { 
     nextData[i][j] = blur(blurData, i, j); 
    } 
} 

당신이 지금처럼 보이는 blur 방법에서 조건문을 제거하면 (. 또한 입력 데이터를 생성하는 randomMatrix 기능에 해당하는 변경을 수행해야) 이 버전의

public int blur(final int[][] data, final int x, final int y) { 
    float accumulator = 0; 
    int[] col = data[x]; 
    accumulator += col[y]; 
    accumulator += data[x-1][y] * 0.5f; 
    accumulator += data[x+1][y] * 0.5f; 
    accumulator += col[y-1] * 0.5f; 
    accumulator += col[y+1] * 0.5f; 
    return Math.round(accumulator/3f); 
} 

결과는 15 % 더 빠른 어쩌면 있습니다

Benchmark      Mode Samples  Score Score error Units 
c.s.q.ShaderFunc.testProcess thrpt   5  8.424  1.035 ops/s 

가 이제 계산에 대해 자세히 살펴 보겠습니다. 입력 값은 모두 int이지만, float 변수에 누적됩니다. 그리고 출력은 int입니다. 0.5f에 의한 반복 곱셈 대신에 우리는 양을 두 배로 누적 한 다음 끝에 6f으로 나눌 수 있습니다. (. 입력 데이터가 20 억 범위에있는 경우 오버 플로우의 가능성은,하지만 여기에있다) 몇 가지 추가 단순화로, 개정 된 코드는 다음과 같습니다

public int blur(final int[][] data, final int x, final int y) { 
    int[] col = data[x]; 
    int accumulator = 2 * col[y] 
         + data[x-1][y] 
         + data[x+1][y] 
         + col[y-1] 
         + col[y+1]; 
    return Math.round(accumulator/6f); 
} 

을 그리고 결과는 80 % 이상이다 더 빨리!

Benchmark      Mode Samples  Score Score error Units 
c.s.q.ShaderFunc.testProcess thrpt   5  15.397  1.897 ops/s 

간략화 된 blur 방법으로 인라이닝을 다시 생각해 봅시다. blur 메서드의 본문을 가져 와서 위에 중첩 된 for 루프 (변수 이름 조정 등)로 명백한 리팩토링을 수행하기 때문에 코드를 재현하지 않습니다.

조금 더 빠르지 만 오류 범위 내에서 확실하게 알기는 어렵습니다. 함수를 별도로 유지하면 인라인 할 가치가 없을 수도 있습니다. 다른 알고리즘을 연결하는 것이 더 쉽습니다.

여기서 큰 이점은 부동 소수점 연산, 특히 부동 소수점 곱셈을 제거하는 것입니다. 많은 멀티 코어 시스템은 부동 소수점 하드웨어보다 더 많은 정수를 가지고 있으므로 그러한 시스템에서 FP를 사용하는 것은 여전히 ​​도움이 될 것입니다.

아, 저에게 또 다른 아이디어가 있습니다. Math.round 전화와 FP 분할을 제거 할 수 있습니까? 다시 말하지만, 입력 범위에 따라 정수 기반의 반올림을 할 수 있습니다.

(1000 * accumulator + 500)/6000 

이 변화 결과는 또 다른 25 % 향상됩니다 대신

Math.round(accumulator/6f) 

의 우리는 같은 더 또는 덜 상응하는 뭔가를 할 수있다!

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderFunc.testProcessInline thrpt   5  19.517  2.894 ops/s 

오늘의 강의 : 속도를 높이려면 부동 소수점을 정수 계산으로 바꿉니다. 물론 오버플로 및 정수 잘림 문제에주의해야합니다. 그러나 당신이 그것을 일하게 만들 수 있다면, 그것은 그것의 가치가 있습니다.

UPDATE는 Alexey Shipilev (JMH의 저자)에서 제안에 대한 응답으로

나는 6.0f 대신 나눔의 역수로 곱 다른 버전을 실행했습니다. 코드는 다음과 같습니다.

static final float ONE_SIXTH = 1.0f/6.0f; 

... 

Math.round(accumulator * ONE_SIXTH); 

이렇게하면 부동 소수점 나누기가 부동 소수점 곱셈으로 바뀝니다. 결과는 다음과 같습니다 :

Benchmark        Mode Samples  Score Score error Units 
c.s.q.ShaderFunc.testProcessInline thrpt   5  17.144  0.869 ops/s 

FP 분할보다 빠르지 만 정수 계산만큼 빠르지 않습니다.

+0

입력 데이터를 채우는 것은 환상적인 아이디어이며 각 레이어에 사용할 기본값을 쉽게 정의 할 수 있습니다. float 작업을 제거하는 것이 큰 영향을 미친다는 점이 매우 흥미 롭습니다. 플로트 지침과 레지스터를 가진 최신 칩셋을 고려해 볼 때 나는 그런 극적인 변화가있을 것으로 기대하지는 못했습니다. 일부 데이터는 수레로 더 쉽게 표현 될 수 있지만, 성능 차이에 대해서는 작은 단위의 정수로 변환하면 행복합니다. 버퍼가 할 때 longs를 사용하는 것에 대해 궁금합니다. – ssube

+0

6을 *로 나누는 대신 6을 역수로 곱하면 * 정수 영역으로가는 것과 같은 효과가 있습니까? –

+0

@ssube 부동 소수점은 여전히 ​​놀랍도록 비쌉니다. 나는 심지어 0을 곱하는 것과 같은 명백하게 "단순한"계산인지 여부조차 모른다.5는 정수 * 2와 같이 단락되어 교대로 바뀝니다. –