ไปที่เนื้อหา


รูปภาพ

Adler-32 ความรู้พื้นๆ สำหรับคนช่างแกะ


  • กรุณาลงชื่อเข้าใช้เพื่อตอบกระทู้
มี 4 โพสต์ตอบกลับกระทู้นี้

#1 AssertionFailed

AssertionFailed

    Exclusive Member

  • Exclusive Programmer
  • 10116 โพสต์

โพสต์เมื่อ 13 October 2007 - 08:12:50 PM

ไม่มีอะไรมากมาย พิมพ์ไปงั้นๆ หาที่ระบายคลายเครียด laugh.gif laugh.gif laugh.gif
และพอดีไปเจอว่าเกมนึงเค้าใช้ ก็เลยแวะมาพิมพ์เล่นๆ พอขำๆ

Adler-32 คืออะไร
วิกิ เค้าว่า
Adler-32 is a checksum algorithm which was invented by Mark Adler.
Compared to a cyclic redundancy check of the same length it trades reliability for speed.

ข้อดี rfc 1950 เค้าบอก
The Adler-32 algorithm is much faster than the CRC32 algorithm
yet still provides an extremely low probability of undetected errors.


หลักการคิด
ให้ C คือ Adler-32 checksum หาได้จาก
calculating two 16-bit checksums A and B and concatenating their bits into a 32-bit integer
จะได้ว่า
C=(B*(2^16))+A

ให้ A คือผลรวมของทุกๆ D จำนวน n แล้วบวกด้วย 1 เสร็จแล้วหารเอาเศษด้วย 65521
เหตุที่ต้องบวกหนึ่ง rfc บอกว่า
The sum A is initialized to 1 instead of zero to make the length
of the sequence part of B, so that the length does not have to be
checked separately.
ส่วน 65521 คือจำนวนเฉพาะที่มากที่สุดของจำนวน 2^16
ที่ต้องหาร เพราะว่า
That 65521 is prime is important to avoid a possible
large class of two-byte errors that leave the check unchanged.
จึงได้ว่า
A=(1+D1+D2+...+Dn)%65521

ให้ B คือผลรวมของทุกๆ A เสร็จแล้วหารเอาเศษด้วย 65521 จึงได้ว่า
B=((1+D1)+(1+D1+D2)+...+(1+D1+D2+...+Dn))%65521

ให้ D คือข้อมูลใดๆ โดยที่ D มีขนาด 8 บิต จะได้ว่า
0<=D<=255

ให้ n คือความยาวของข้อมูล จะได้ว่า
n>=0


เมื่อนำมาเขียนเป็นโค้ด(ระดับประถม)จะได้ว่า
uint32_t Adler32(uint8_t *D,size_t n)
{
   uint32_t A=1,B=0;

   for(size_t i=0;i<n;++i)
   {
      A=(A+D[i])%65521;
      B=(B+A)%65521;
   }

   return (B*2^16)+A;
}

ส่วนอันนี้โค้ดระดับมหา'ลัย(หรือสูงกว่าู^^)
*ผมเปลี่ยนชื่อตัวแปรเพื่อให้พ้องกับคำจำกัดความข้างต้น
/* adler32.c -- compute the Adler-32 checksum of a data stream
* Copyright (C) 1995-1998 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/

/* @(#) $Id$ */

#include "zlib.h"

#define BASE 65521L /* largest prime smaller than 65536 */
#define NMAX 5552
/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */

#define DO1(buf,i)  {s1 += buf[i]; s2 += s1;}
#define DO2(buf,i)  DO1(buf,i); DO1(buf,i+1);
#define DO4(buf,i)  DO2(buf,i); DO2(buf,i+2);
#define DO8(buf,i)  DO4(buf,i); DO4(buf,i+4);
#define DO16(buf)   DO8(buf,0); DO8(buf,8);

/* ========================================================================= */
uint32_t ZEXPORT adler32(uint32_t C, uint8_t *D, size_t n)
{
    uint32_t A = C & 0xffff;
    uint32_t B = (C >> 16) & 0xffff;
    size_t k;

    if (D == Z_NULL) return 1L;

    while (n > 0)
    {
        k = n < NMAX ? n : NMAX;
        n -= k;
        while (k >= 16)
       {
            DO16(D);
            D += 16;
            k -= 16;
        }
        
       if (k != 0)
        {
           do
           {
              A += *buf++;
              B += A;
            } while (--k);
        }
      
        A %= BASE;
        B %= BASE;
    }
    
    return (B << 16) | A;
}


หากใครถลำอ่านมาถึงตรงนี้ ขอบอกว่าความมันยังไม่เกิด
สำหรับผม ความมัน มันอยู่ที่ จากประถม มันไปมหา'ลัย ได้อย่างไร
เช่นว่า 5552 มันมาได้อย่างไร แล้วมาทำไม เพื่ออะไร
ไว้โอกาสหน้าจะมาเล่าให้ฟัง ช้าเร็ว แล้วแต่อารมณ์ tongue.gif

*ผิดพลาด ตกหล่น ตรงไหน โปรดท้วงติงด้วยนะ เผื่อตาลาย

#2 JackY

JackY

    หนี Microsoft ไม่พ้นซักทีเรา

  • Exclusive Programmer
  • 10072 โพสต์
  • Location:ซอกเล็กๆในหัวใจเธอ

โพสต์เมื่อ 15 October 2007 - 07:51:01 AM

QUOTE(AssertionFailed @ Oct 13 2007, 08:12 PM) ดูโพสต์

ไม่มีอะไรมากมาย พิมพ์ไปงั้นๆ หาที่ระบายคลายเครียด laugh.gif laugh.gif laugh.gif
และพอดีไปเจอว่าเกมนึงเค้าใช้ ก็เลยแวะมาพิมพ์เล่นๆ พอขำๆ

Adler-32 คืออะไร
วิกิ เค้าว่า
QUOTE
Adler-32 is a checksum algorithm which was invented by Mark Adler.
Compared to a cyclic redundancy check of the same length it trades reliability for speed.

ข้อดี rfc 1950 เค้าบอก
QUOTE
The Adler-32 algorithm is much faster than the CRC32 algorithm
yet still provides an extremely low probability of undetected errors.


หลักการคิด
ให้ C คือ Adler-32 checksum หาได้จาก
calculating two 16-bit checksums A and B and concatenating their bits into a 32-bit integer
จะได้ว่า
QUOTE
C=(B*(2^16))+A

ให้ A คือผลรวมของทุกๆ D จำนวน n แล้วบวกด้วย 1 เสร็จแล้วหารเอาเศษด้วย 65521
เหตุที่ต้องบวกหนึ่ง rfc บอกว่า
The sum A is initialized to 1 instead of zero to make the length
of the sequence part of B, so that the length does not have to be
checked separately.
ส่วน 65521 คือจำนวนเฉพาะที่มากที่สุดของจำนวน 2^16
ที่ต้องหาร เพราะว่า
That 65521 is prime is important to avoid a possible
large class of two-byte errors that leave the check unchanged.
จึงได้ว่า
QUOTE
A=(1+D1+D2+...+Dn)%65521

ให้ B คือผลรวมของทุกๆ A เสร็จแล้วหารเอาเศษด้วย 65521 จึงได้ว่า
QUOTE
B=((1+D1)+(1+D1+D2)+...+(1+D1+D2+...+Dn))%65521

ให้ D คือข้อมูลใดๆ โดยที่ D มีขนาด 8 บิต จะได้ว่า
QUOTE
0<=D<=255

ให้ n คือความยาวของข้อมูล จะได้ว่า
QUOTE
n>=0


เมื่อนำมาเขียนเป็นโค้ด(ระดับประถม)จะได้ว่า
CODE
uint32_t Adler32(uint8_t *D,size_t n)
{
   uint32_t A=1,B=0;

   for(size_t i=0;i<n;++i)
   {
      A=(A+D[i])%65521;
      B=(B+A)%65521;
   }

   return (B*2^16)+A;
}

ส่วนอันนี้โค้ดระดับมหา'ลัย(หรือสูงกว่าู^^)
*ผมเปลี่ยนชื่อตัวแปรเพื่อให้พ้องกับคำจำกัดความข้างต้น
CODE
/* adler32.c -- compute the Adler-32 checksum of a data stream
* Copyright (C) 1995-1998 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/

/* @(#) $Id$ */

#include "zlib.h"

#define BASE 65521L /* largest prime smaller than 65536 */
#define NMAX 5552
/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */

#define DO1(buf,i)  {s1 += buf[i]; s2 += s1;}
#define DO2(buf,i)  DO1(buf,i); DO1(buf,i+1);
#define DO4(buf,i)  DO2(buf,i); DO2(buf,i+2);
#define DO8(buf,i)  DO4(buf,i); DO4(buf,i+4);
#define DO16(buf)   DO8(buf,0); DO8(buf,8);

/* ========================================================================= */
uint32_t ZEXPORT adler32(uint32_t C, uint8_t *D, size_t n)
{
    uint32_t A = C & 0xffff;
    uint32_t B = (C >> 16) & 0xffff;
    size_t k;

    if (D == Z_NULL) return 1L;

    while (n > 0)
    {
        k = n < NMAX ? n : NMAX;
        n -= k;
        while (k >= 16)
       {
            DO16(D);
            D += 16;
            k -= 16;
        }
        
       if (k != 0)
        {
           do
           {
              A += *buf++;
              B += A;
            } while (--k);
        }
      
        A %= BASE;
        B %= BASE;
    }
    
    return (B << 16) | A;
}


หากใครถลำอ่านมาถึงตรงนี้ ขอบอกว่าความมันยังไม่เกิด
สำหรับผม ความมัน มันอยู่ที่ จากประถม มันไปมหา'ลัย ได้อย่างไร
เช่นว่า 5552 มันมาได้อย่างไร แล้วมาทำไม เพื่ออะไร
ไว้โอกาสหน้าจะมาเล่าให้ฟัง ช้าเร็ว แล้วแต่อารมณ์ tongue.gif

*ผิดพลาด ตกหล่น ตรงไหน โปรดท้วงติงด้วยนะ เผื่อตาลาย


อ่านโค้ดภาษา C++ แล้วงงจิงๆ งงตรง pointer นี่แหละ เฮ้อ...


#3 CodeGeaR

CodeGeaR

    Exclusive Member

  • Exclusive Programmer
  • 10218 โพสต์

โพสต์เมื่อ 15 October 2007 - 11:12:18 AM


ตรง โค้ด(ระดับประถม) พอมองออกว่า อะไร คืออะไร ทำงานยังไง

พอเป็น ระดับ มหาลัย งง มากมาย เลยครับ
** ไม่เข้าใจ ภาษา C เพราะพื้น เล้าหมู ซะด้วยอ่ะ **


#4 AssertionFailed

AssertionFailed

    Exclusive Member

  • Exclusive Programmer
  • 10116 โพสต์

โพสต์เมื่อ 15 October 2007 - 07:27:04 PM

มาปรับโค้ดกันหน่อย
จากประถม สู่มหา'ลัย

จากโค้ดประถมเราจะเห็นว่า ทั้ง A และ B มีการหารทุกรอบของลูป
และเราก็รู้ว่า การหารนั้น กินแรงซีพียูมากกว่าการบวกธรรมดาๆ
ดังนั้นถ้าเรายิ่งหารน้อยมากเท่าไหร่ โค้ดเราก้ทำงานเร็วยิ่งขึ้นตามไปด้วย

เนื่องจาก A และ B มีขนาด 32 บิต ฉะนั้นค่าสูงสุดจึงไม่เกิน (2^32)-1 ----------(1)
และถ้าดูจากโค้ด B จะมากกว่า A --------------(2)

จากคุณสมบัติข้อ (1) และ (2) เราจึงสามารถคำนวณค่าของ B ได้เรื่อยๆ ตราบเท่าที่มันไม่เกินค่าสูงสุด
สุดท้ายค่อยทำการหารทีเดียว

ปัญหาของเราก็คือ จะทำการหารเมื่อไหร่

เราก็ต้องกลับไปดูว่า B มีการคำนวณอย่างไร
B=((1+D1)+(1+D1+D2)+...+(1+D1+D2+...+Dn))%65521


นั่นก็คือ เราสามารถคำนวณได้ตราบเท่าที่เงื่อนไขนี้เป็นจริง -------------(3)
(1+D1)+(1+D1+D2)+...+(1+D1+D2+...+Dn)  <=  (2^32)-1


จากความรู้เรื่อง Arithmetic progression เราจะได้ว่า
(1+D1)+(1+D1+D2)+...+(1+D1+D2+...+Dn)=nD1 + (n-1)D2 + (n-2)D3 + ... + Dn + n


ค่าสูงสุดของ D คือ 255 ถ้าหาก ทุกๆ D มีค่า 255 เราจะได้ว่า
n255+(n-1)255+(n-2)255+...+255+n


จัดใหม่ ก็ได้ว่า
(n+(n-1)+(n-2)+...+1)255+n


และ
(n+(n-1)+(n-2)+...+1)=n(n+1)/2


แทนค่าใน (3) ใหม่อีกที จะได้ว่า
(n(n+1)/2)255+n  <=  (2^32)-1


ตอนนี้ มาหาค่า n กัน
ก่อนอื่นแทนค่า ตัวที่ทำได้
(n(n+1)/2)255+n <= 4294967295


คูณสองทั้งสองข้าง
((n(n+1)/2)255+n)*2 <= 4294967295*2
n(n+1)255 + 2n  <=  8589934590
255n^2 + 255n + 2n <=  8589934590
255n^2 + 257n <=  8589934590


ลบ 8589934590 ทั้งสองข้าง
255n^2 + 257n - 8589934590  <=  8589934590-8589934590
255n^2 + 257n - 8589934590  <= 0


ยังพอจำ Quadratic equation กันได้ไหมครับ
aX^2 + bX +c = 0
X  =  (-b +- sqr( b^2 - 4ac))/2a


เอามาหาค่า n ได้เลย
n = (-257 + sqr(257^2 - 4 * 255 * (-8589934590))) / (2 * 255)
n = 5803.4618135074289166184943988784

นั่นก็คือ เราสามารถคำนวณได้เรื่อย ตราบเท่าที่
n <= 5803.4618135074289166184943988784


แต่จากโค้ดมหา'ลัย เขาให้ n <= 5552 (แทนที่จะเป็น 5803) ตามที่เราคำนวณได้
เพราะเขาใช้สูตร
255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1


ซึ่งถ้าคุณๆ ใช้วิธีแก้สมการตามที่ผมยกมา ก็จะได้ค่า 5552 ออกมา

เนื่องจากตอนนี้ความรู้ของผมยังมีเท่าหางอึ่ง และหาใครชี้แนะไม่ได้
ก็เลยยังไม่รู้ว่าสูตรดังกล่าวมาได้อย่างไรกัน เอาไว้ว่ากันอีกที
แต่อย่างน้อยเราก็รู้ว่า 5552 มาได้อย่างไร และใช้เพื่ออะไร ดังใน rfc1950 ว่าไว้
The modulo on unsigned long accumulators can be delayed for 5552 bytes,
so the modulo operation time is negligible.

เอาหละ ใช้ความรู้ที่ได้ มาปรับโค้ดประถมของเราซะหน่อย
(ผมแยกออกเป็นบรรทัดๆ ทำให้โค้ดยาวขึ้น แต่อ่านง่ายขึ้น และ ความเร็วก็ยังเท่าเดิมนะ)
#define BASE 65521
#define NMAX 5552
uint32_t Adler32(uint8_t *D,size_t n)
{
   uint32_t A=1,B=0;
   size_t k=0;

   while(n>0)
   {
      k= (n>NMAX) ? NMAX : n; //จากข้อมูล n จำนวน เราตัดเอามาคำนวณที่ละช่วง แล้วหารทีเดียว
      n-=k;   //อัพเดท ค่า n ที่เหลืออยู่
      
      do   //do ทำงานเร็วกว่า for 1 คำสั่ง เมื่อดูในระดับแอสเซมบลี
     {
          A+=*D;
          ++D;
          B+=A;
          --k;
       }while(k);

        //จบ 1 ชุด ค่อยมาหารที่เดียว
        A %= BASE;
        B %= BASE;
    }
  
   return (B*2^16)+A;   //อันนี้คิดว่าค่อยปรับหลังจากได้อธิบายดีกว่า
}


กลับไปดู วิกิ กล่าวถึง ประวัติ Adler-32 ว่า
It is a modification of the Fletcher checksum, which in its original form is slightly faster but less reliable.

ที่บอกว่า ช้ากว่า ก็เพราะ Adler-32 ต้องหาร นั่นเอง
แต่ที่ว่าไว้ใจได้มากกว่า ดูจะไม่ถูกต้องนัก
เพราะจากที่ผมดูในเอกสารทีี่เขาทำการวิจัย พบว่า Fletcher ดีกว่าในหลายๆ กรณี
อย่างที่บอกว่า การเอาจำนวนเฉพาะมาหาร จะทำไห้ได้ค่าที่ดีกว่า แต่พิสูจน์ แล้วก็ไม่จริง

เรื่องของ Fletcher ที่มีอยู่ใน IEEE ยังหาที่โหลดฟรีไม่ได้ เสียใจยิ่งนัก (อะไรๆที่มากั้นความรู้นี่ เกลียดมันๆ)
ถ้าได้มาคงสร้างความกระจ่างมากกว่านี้
Fletcher, J., "An Arithmetic Checksum for Serial Transmissions",
IEEE Transactions on Communication,
Vol. COM-30, No. 1, pp. 247-252, January 1982.

ร่ายมาซะยืดยาว คงพอมองออกว่า โค้ดแต่ละบรรทัดนี่ กว่าจะได้ออกมา ทำเอาเหงื่อตก
ยิ่งถ้าไปดูเอกสารเก่าๆ เล่นนับคำสั่งกันเลย(หมายถึงระดับคำสั่งซีพียู) คงเพราะเครื่องยังไม่แรง เลยต้องสนใจกันมากหน่อย
แม้ตอนนี้เครื่องจะแรง ความเร็วก็ยังสำคัญ
ลองนึกภาพ ServerA ทำงาน ช้ากว่า ServerB 1 คำสั่ง อาจจะดูเล็กน้อย
แต่ถ้าดูภาพรวม หากมีการใช้งาน ล้านครั้ง ก็ช้ากว่า ล้านคำสั่ง

วัุนนี้เล่าสู่กันฟังแค่นี้ก่อน biggrin.gif

#5 CodeGeaR

CodeGeaR

    Exclusive Member

  • Exclusive Programmer
  • 10218 โพสต์

โพสต์เมื่อ 18 October 2007 - 03:53:08 AM


--/||\--

สาธุ !! ขอบคุณครับ ที่ให้ความรู้
แต่ไม่เข้าสมองเลย ไม่ใช่อธิบายไม่รู้เรื่องนะครับ
แต่ผม ไม่มี พื้นฐานความรู้ พวกนี้เอง




0 สมาชิกกำลังอ่านกระทู้นี้

0 สมาชิก, 0 ผู้เยี่ยมชม, 0 ผู้ใช้งานที่ซ่อนตัว