2017-01-20 10 views
2

일부 스케일링 기능을 사용하여 16 비트 unsigned short 데이터를 8 비트 unsigned char으로 변환하려고합니다. 현재 저는 float로 변환하고 크기를 줄인 다음 8 비트로 채 웁니다. 더 효율적인 방법이 있습니까?스케일링을 효율적으로 사용하여 16 비트 부호없는 short를 8 비트 unsigned char로 변환하는 방법은 무엇입니까?

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    float Scale=255.0/65535.0; 

    USHORT sArr[8]={512,1024,2048,4096,8192,16384,32768,65535}; 
    BYTE bArr[8],bArrSSE[8];   

    //Desired Conventional Method 
    for (int i = 0; i < 8; i++) 
    { 
     bArr[i]=(BYTE)(sArr[i]*Scale);     
    } 

    __m128 vf_scale = _mm_set1_ps(Scale), 
      vf_Round = _mm_set1_ps(0.5),      
      vf_zero = _mm_setzero_ps();   
    __m128i vi_zero = _mm_setzero_si128(); 

    __m128i vi_src = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&sArr[0])); 

    __m128 vf_Src_Lo=_mm_cvtepi32_ps(_mm_unpacklo_epi16(vi_src, _mm_set1_epi16(0)));  
    __m128 vf_Src_Hi=_mm_cvtepi32_ps(_mm_unpackhi_epi16(vi_src, _mm_set1_epi16(0)));  

    __m128 vf_Mul_Lo=_mm_sub_ps(_mm_mul_ps(vf_Src_Lo,vf_scale),vf_Round); 
    __m128 vf_Mul_Hi=_mm_sub_ps(_mm_mul_ps(vf_Src_Hi,vf_scale),vf_Round); 

    __m128i v_dst_i = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(vf_Mul_Lo), _mm_cvtps_epi32(vf_Mul_Hi)), vi_zero); 
    _mm_storel_epi64((__m128i *)(&bArrSSE[0]), v_dst_i); 

    for (int i = 0; i < 8; i++) 
    {  
     printf("ushort[%d]= %d  * %f = %.3f ,\tuChar[%d]= %d,\t SSE uChar[%d]= %d \n",i,sArr[i],Scale,(float)(sArr[i]*Scale),i,bArr[i],i,bArrSSE[i]); 
    } 

    return 0; 
} 

스케일링 계수는 다른 값으로 설정해야 할 수도 있습니다. 255.0/512.0, 255.0/1024.0 또는 255.0/2048.0이므로 어떤 솔루션도 255.0/65535.0으로 하드 코딩하면 안됩니다. 코드의 비율이 고정되어있는 경우

+2

디버그보기의 부호가 무시됩니다. 아무 것도 영향을주지 않습니다. 값은 정확합니다. 마음 속의 비트를 재 해석하거나 인쇄하십시오. – harold

+2

나는 당신이 대답을 삭제 한 것을 본다 - 정말로 이것을 할 필요는 없다 - 다소 비효율적이긴하지만 개선 할 다른 대답을위한 좋은 출발점이된다. 삭제 취소를 고려하십시오.(나는 아마도'_mm_mulhi_epu16'을 사용하여 직접 답변을 작성 하겠지만 오늘은 꽤 바쁩니다. 어쩌면 주말에 함께 할 것입니다.) –

답변

1

여기 고정 소수점 스케일링 동작을 수행하도록 _mm_mulhi_epu16를 사용하여 구현 및 테스트 장치이다.

scale_refscale_1은 (현재 삭제) 대답에서 부동 소수점 SSE의 구현이며, scale_2 내 고정 소수점 구현, 원래 스칼라 코드입니다.

다양한 구현을 개별 함수로 분해하고 크기 매개 변수와 루프를 추가하여 모든 크기 배열에 사용할 수 있도록했습니다 (현재 n은 SSE 구현의 경우 8이어야 함) .

고정 소수점 구현이 스칼라 코드처럼 잘리는 지 또는 가장 가까운 순으로 정렬되는지를 제어하는 ​​컴파일 시간 플래그 ROUND이 있습니다. 절단이 약간 더 빠릅니다.

scale은 런타임 매개 변수이며, 아래의 테스트 장치에서 현재 255로 하드 코딩되어 있습니다 (255.0/65535.0과 동일). 그러나 적절한 값이 될 수 있습니다.

#include <stdio.h> 
#include <stdint.h> 
#include <limits.h> 
#include <xmmintrin.h> 

#define ROUND 1  // use rounding rather than truncation 

typedef uint16_t USHORT; 
typedef uint8_t BYTE; 

static void scale_ref(const USHORT *src, BYTE *dest, const USHORT scale, const size_t n) 
{ 
    const float kScale = (float)scale/(float)USHRT_MAX; 

    for (size_t i = 0; i < n; i++) 
    { 
     dest[i] = src[i] * kScale; 
    } 
} 

static void scale_1(const USHORT *src, BYTE *dest, const USHORT scale, const size_t n) 
{ 
    const float kScale = (float)scale/(float)USHRT_MAX; 

    __m128 vf_Scale = _mm_set1_ps(kScale); 
    __m128 vf_Round = _mm_set1_ps(0.5f); 

    __m128i vi_zero = _mm_setzero_si128(); 

    for (size_t i = 0; i < n; i += 8) 
    { 
     __m128i vi_src = _mm_loadu_si128((__m128i *)&src[i]); 

     __m128 vf_Src_Lo = _mm_cvtepi32_ps(_mm_unpacklo_epi16(vi_src, _mm_set1_epi16(0))); 
     __m128 vf_Src_Hi = _mm_cvtepi32_ps(_mm_unpackhi_epi16(vi_src, _mm_set1_epi16(0))); 
     __m128 vf_Mul_Lo = _mm_mul_ps(vf_Src_Lo, vf_Scale); 
     __m128 vf_Mul_Hi = _mm_mul_ps(vf_Src_Hi, vf_Scale); 

     //Convert -ive to +ive Value 
     vf_Mul_Lo = _mm_max_ps(_mm_sub_ps(vf_Round, vf_Mul_Lo), vf_Mul_Lo); 
     vf_Mul_Hi = _mm_max_ps(_mm_sub_ps(vf_Round, vf_Mul_Hi), vf_Mul_Hi); 

     __m128i v_dst_i = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(vf_Mul_Lo), _mm_cvtps_epi32(vf_Mul_Hi)), vi_zero); 
     _mm_storel_epi64((__m128i *)&dest[i], v_dst_i); 
    } 
} 

static void scale_2(const USHORT *src, BYTE *dest, const USHORT scale, const size_t n) 
{ 
    const __m128i vk_scale = _mm_set1_epi16(scale); 
#if ROUND 
    const __m128i vk_round = _mm_set1_epi16(scale/2); 
#endif 

    for (size_t i = 0; i < n; i += 8) 
    { 
     __m128i v = _mm_loadu_si128((__m128i *)&src[i]); 
#if ROUND 
     v = _mm_adds_epu16(v, vk_round); 
#endif 
     v = _mm_mulhi_epu16(v, vk_scale); 
     v = _mm_packus_epi16(v, v); 
     _mm_storel_epi64((__m128i *)&dest[i], v); 
    } 
} 

int main(int argc, char* argv[]) 
{ 
    const size_t n = 8; 
    const USHORT scale = 255; 

    USHORT src[n] = { 512, 1024, 2048, 4096, 8192, 16384, 32768, 65535 }; 
    BYTE dest_ref[n], dest_1[n], dest_2[n]; 

    scale_ref(src, dest_ref, scale, n); 
    scale_1(src, dest_1, scale, n); 
    scale_2(src, dest_2, scale, n); 

    for (size_t i = 0; i < n; i++) 
    { 
     printf("src = %u, ref = %u, test_1 = %u, test_2 = %u\n", src[i], dest_ref[i], dest_1[i], dest_2[i]); 
    } 

    return 0; 
} 
+0

답장을 보내 주셔서 감사합니다! 부동 소수점 스케일 값을 int로 변환하는 방법을 알려주시겠습니까? 즉 255.0/65535.0 = 255의 방법은 무엇입니까? –

+0

@BalajiR : 우리는 "곱하기 하이"를 사용하기 때문에 65536으로 암시 적으로 나눗셈이되므로 스케일 인자는 255가됩니다. (255.0/65535.0과 255.0/65536.0의 차이는 중요하지 않습니다. 결과 데이터에는 8 비트의 정밀도 만 있습니다.) –

+0

예, 이해합니다! 그러나 분모가 일정하지 않다면 어떨까요? 즉 255.0/512.0, 255.0/1024.0, 255.0/2048.0? 그러면 스케일 인자로 어떻게 변환 할 수 있습니까? 미안하지만 제대로 설명하지 못했다면 내 분자는 일정합니다.하지만 분모는 변경됩니다! –

2

, 당신은 다음의 알고리즘

  1. 시프트 하나 아래에 각 단어의 상위 바이트와 규모를 수행 할 수 있습니다.
    예. 0x200 -> 0x2, 0xff80 -> 0xff
  2. 낮은 바이트가 0x80보다 작 으면 -1의 오프셋을 추가합니다.
    예. 을 0x200 -> 오프셋 -1 0xff80는 -> 0

첫 번째 부분은 쉽게 _mm_srli_epi16

두번째는 까다하지만 기본적 비트 7 (하부의 상위 비트를 취하는 구성되어 달성된다 오프셋 바이트 단위로), 단어 전체에 복제 한 다음이를 부정합니다.

나는 또 다른 접근법을 사용했다 : 나는 벡터와 그 자신을 동일성으로 비교함으로써 -1라는 단어의 벡터를 만들었다.
그런 다음 각 소스 단어의 bit7을 분리하여 -1 단어에 추가합니다.

#include <stdio.h> 
#include <emmintrin.h> 

int main(int argc, char* argv[]) 
{ 
    float Scale=255.0/65535.0; 

    unsigned short sArr[8]={512,1024,2048,4096,8192,16384,32768,65535}; 
    unsigned char bArr[8], bArrSSE[16];   

    //Desired Conventional Method 
    for (int i = 0; i < 8; i++) 
    { 
     bArr[i]=(unsigned char)(sArr[i]*Scale);     
    } 



    //Values to be converted 
    __m128i vi_src = _mm_loadu_si128((__m128i const*)sArr); 

    //This computes 8 words (16-bit) that are 
    // -1 if the low byte of relative word in vi_src is less than 0x80 
    // 0 if the low byte of relative word in vi_src is >= than 0x80 

    __m128i vi_off = _mm_cmpeq_epi8(vi_src, vi_src); //Set all words to -1 
    //Add the bit15 of each word in vi_src to each -1 word 
    vi_off 
    = _mm_add_epi16(vi_off, _mm_srli_epi16(_mm_slli_epi16(vi_src, 8), 15)); 

    //Shift vi_src word right by 8 (move hight byte into low byte) 
    vi_src = _mm_srli_epi16 (vi_src, 8); 
    //Add the offsets 
    vi_src = _mm_add_epi16(vi_src, vi_off); 
    //Pack the words into bytes 
    vi_src = _mm_packus_epi16(vi_src, vi_src); 

    _mm_storeu_si128((__m128i *)bArrSSE, vi_src); 

    for (int i = 0; i < 8; i++) 
    {  
     printf("%02x %02x\n", bArr[i],bArrSSE[i]); 
    } 

    return 0; 
}