لماذا لا يوجد z80 مثل وظيفة LDIR في C / C ++ / RTL؟

StackOverflow https://stackoverflow.com/questions/387654

  •  23-08-2019
  •  | 
  •  

سؤال

في رمز الجهاز Z80، تقنية رخيصة لتهيئة المخزن المؤقت لقيمة ثابتة، كما يقول كل الفراغات. لذلك قد تبدو جزء من التعليمات البرمجية شيئا كهذا.

LD HL, DESTINATION             ; point to the source
LD DE, DESTINATION + 1         ; point to the destination
LD BC, DESTINATION_SIZE - 1    ; copying this many bytes
LD (HL), 0X20                  ; put a seed space in the first position
LDIR                           ; move 1 to 2, 2 to 3...

النتيجة هي أن قطعة الذاكرة في الوجهة مليئة تماما تماما. لقد قمت بتجربة MemMOVE و MEMCPY ولا أستطيع تكرار هذا السلوك. كنت أتوقع memmove أن تكون قادرة على القيام بذلك بشكل صحيح.

لماذا تتصرف MemMOVE و MAMCPY بهذه الطريقة؟

هل هناك طريقة معقولة للقيام بهذا النوع من تهيئة الصفيف؟

أنا أدرك بالفعل مجموعة array [الحجم] = {0} لتهيئة الصفيف

أنا أدرك بالفعل أن ميمزة ستقوم بعمل الشخصيات الفردية.

ما هي الأساليب الأخرى الموجودة في هذه المسألة؟

هل كانت مفيدة؟

المحلول

أعتقد أن هذا يذهب إلى فلسفة تصميم C و C ++. كما بيارن ستروستروب بمجرد قال, ، أحد المبادئ التوجيهية الرئيسية لتصميم C ++ هو "ما لا تستخدمه، لا تدفع مقابل". و في حين دينيس ريتشي قد لا قال ذلك بالضبط تلك الكلمات نفسها، وأعتقد أن هذا مبدأ توجيهي يبلغ تصميمه C (وتصميم C من قبل الناس اللاحقة) كذلك. الآن قد تعتقد أنه إذا قمت بتخصيص الذاكرة، فينبغي تهيئة تلقائيا إلى NULL's وكنت أميل إلى الاتفاق معك. ولكن هذا يأخذ دورات ماكينة وإذا كنت ترميز في موقف حيث تكون كل دورة حاسمة، فقد لا تكون هذه مفاضلة مقبولة. في الأساس C و C ++ حاول البقاء بعيدا عن طريقك - وبالتالي إذا كنت تريد شيئا مهيئا عليك القيام بذلك بنفسك.

نصائح أخرى

memmove و memcpy لا تعمل بهذه الطريقة لأنها ليست دلالة مفيدة لتحريك الذاكرة أو نسخ الذاكرة. إنه مفيد في Z80 ليتمكن من ملء الذاكرة، ولكن لماذا تتوقع وظيفة تسمى "memmove" لملء الذاكرة مع بايت واحد؟ انها لنقل كتل الذاكرة حولها. يتم تنفيذها للحصول على الإجابة الصحيحة (يتم نقل بايت المصدر إلى الوجهة) بغض النظر عن كيفية تداخل الكتل. من المفيد أن تحصل على الإجابة الصحيحة لتحريك كتل الذاكرة.

إذا كنت ترغب في ملء الذاكرة، استخدم MemSet، وهو مصمم للقيام به فقط ما تريد.

كانت هناك طريقة أسرع لإزالة مساحة الذاكرة باستخدام المكدس. على الرغم من أن استخدام LDI و LDIR كان شائعا جدا، فقد جاء David WebB (الذي دفع طيف ZX في جميع أنواع طرق العد التنازلي رقم الشاشة بما في ذلك الحدود) بهذه التقنية التي هي أسرع 4 مرات:

  • يحفظ مؤشر المكدس ثم ينقله إلى نهاية الشاشة.
  • تحميل زوج سجل HL مع صفر،
  • يذهب إلى حلقة ضخمة دفع HL على المكدس.
  • تقوم المكدس بتحريك الشاشة وهبوطا عبر الذاكرة وفي هذه العملية، ومسح الشاشة.

تم أخذ التفسير أعلاه من مراجعة David Webbs Game Starion.

قد يبدو روتين Z80 تماما مثل هذا:

  DI              ; disable interrupts which would write to the stack.
  LD HL, 0
  ADD HL, SP      ; save stack pointer
  EX DE, HL       ; in DE register
  LD HL, 0
  LD C, 0x18      ; Screen size in pages
  LD SP, 0x4000   ; End of screen
PAGE_LOOP:
  LD B, 128       ; inner loop iterates 128 times
LOOP:
  PUSH HL         ; effectively *--SP = 0; *--SP = 0;
  DJNZ LOOP       ; loop for 256 bytes
  DEC C
  JP NZ,PAGE_LOOP
  EX DE, HL
  LD SP, HL       ; restore stack pointer
  EI              ; re-enable interrupts

ومع ذلك، فإن هذا الروتين أقل قليلا مرتين. LDIR نسخ بايت واحد كل 21 دورة. نسخ الحلقة الداخلية 2 بايت كل 24 دورة - 11 دورة PUSH HL و 13 ل DJNZ LOOP. وبعد للحصول على ما يقرب من 4 مرات بسرعة ببساطة لا يقوم ببساطة الحلقة الداخلية:

LOOP:
   PUSH HL
   PUSH HL
   ...
   PUSH HL         ; repeat 128 times
   DEC C
   JP NZ,LOOP

هذا ما يقرب من 11 دورة فقط كل بايتين يبلغ حوالي 3.8 مرة أسرع من القرار 21 لكل بايت من LDIR.

مما لا شك فيه أن التقنية قد اخترعت عدة مرات. على سبيل المثال، ظهرت في وقت سابق Sub-Logic Flight Simulator 1 ل TRS-80 في عام 1980.

لماذا تتصرف MemMOVE و MAMCPY بهذه الطريقة؟

ربما لأنه لا يوجد مترجم محدث C ++ محدد يستهدف أجهزة Z80؟ اكتب واحد. ؛-)

اللغات لا تحدد كيف تقوم أجهزة معينة بأي شيء. هذا هو ما يصل تماما إلى مبرمجات المحول البرمجي والمكتبات. بالطبع، كتابة نسخة خاصة، محددة للغاية لكل تكوين الأجهزة يمكن تخيلها الكثير من العمل. هذا سوف يكون السبب.

هل هناك طريقة معقولة للقيام بهذا النوع من تهيئة الصفيف؟ هل هناك طريقة معقولة للقيام بهذا النوع من تهيئة الصفيف؟

حسنا، إذا فشل كل شيء آخر، فيمكنك دائما استخدام التجميع المضمن. بخلاف ذلك، أتوقع std::fill لأداء الأفضل في تنفيذ STL جيد. ونعم، أنا أدرك تماما أن توقعاتي مرتفعة للغاية وهذا std::memset غالبا ما تؤدي بشكل أفضل في الممارسة العملية.

كانت تسلسل Z80 الذي تظهر به أسرع طريقة للقيام بذلك - في عام 1978. كان ذلك قبل 30 عاما. تقدمت المعالجات كثيرا منذ ذلك الحين، واليوم هذا مجرد أبطأ طريقة للقيام بذلك.

تم تصميم Memmove للعمل عندما يتداخل المصدر والوجهة التداخل، حتى تتمكن من نقل قطعة من الذاكرة بواسطة بايت واحد. هذا جزء من سلوكه المحدد حسب معايير C و C ++. MEMCPY غير محدد؛ قد يعمل بشكل متطابق على memmove، أو قد يكون مختلفا، اعتمادا على كيفية قرر مترجمك تنفيذه. التحويل البرمجي مجاني لاختيار طريقة أكثر كفاءة من memmove.

إذا كنت تخطئ في مستوى الأجهزة، فإن بعض وحدة المعالجة المركزية تحتوي على وحدات تحكم DMA التي يمكنها ملء كتل الذاكرة بسرعة (أسرع بكثير من وحدة المعالجة المركزية التي يمكن أن تفعلها أي وقت مضى). لقد فعلت هذا على Freescale I.MX21 وحدة المعالجة المركزية.

يتم إنجاز هذا في جمعية X86 بنفس السهولة. في الواقع، يتلخص إلى رمز متطابق تقريبا على مثالك.

mov esi, source    ; set esi to be the source
lea edi, [esi + 1] ; set edi to be the source + 1
mov byte [esi], 0  ; initialize the first byte with the "seed"
mov ecx, 100h      ; set ecx to the size of the buffer
rep movsb          ; do the fill

ومع ذلك، فهي ببساطة أكثر كفاءة لتعيين أكثر من بايت واحدة في وقت واحد إذا كنت تستطيع.

أخيرا، memcpy/memmove ليست ما تبحث عنه، هؤلاء هم من أجل صنع نسخ من كتل الذاكرة من المنطقة إلى أخرى (يسمح Memmove بالمصدر والمضمون إلى جزء من المخزن المؤقت نفسه). memset يملأ كتلة مع بايت من اختيارك.

هناك ايضا Calloc. التي تخصص وتهيئة الذاكرة إلى 0 قبل إرجاع المؤشر. بالطبع، تهيئة CALLOC فقط إلى 0، وليس شيئا يحدد المستخدم.

إذا كانت هذه هي الطريقة الأكثر كفاءة لتعيين كتلة من الذاكرة إلى قيمة معينة على Z80، فمن الممكن تماما memset() قد يتم تنفيذها كما تصف مترجم يستهدف z80s.

قد يكون ذلك memcpy() قد تستخدم أيضا تسلسل مماثل على هذا التحويل البرمجي.

ولكن لماذا من المتوقع أن تستخدم محطات التحويل البرمجيات التي استهدفت وحدات المعالجة المركزية مع مجموعات تعليمات مختلفة تماما من Z80 تعبير z80 عن هذه الأنواع من الأشياء؟

تذكر أن الهندسة المعمارية X86 لديها مجموعة مماثلة من التعليمات التي يمكن بادئة مع مندوب OPCODE أن يتم تنفيذها بشكل متكرر للقيام بأشياء مثل نسخ أو ملء أو مقارنة كتل الذاكرة. ومع ذلك، في الوقت المحدد، خرج Intel مع 386 (أو ربما كان ذلك هو 486)، فسيكون وحدة المعالجة المركزية في الواقع هذه التعليمات أبطأ أكثر من تعليمات أبسط في حلقة. لذلك غالبا ما تتوقف المغلوون باستخدام التعليمات المنحدرة.

على محمل الجد، إذا كنت تكتب C / C ++، فما عليك سوى كتابة حلقة بسيطة واترك المبرو عناء عناء. كمثال، إليك بعض الكود VS2005 الذي تم إنشاؤه لهذه العلبة الدقيقة (باستخدام حجم القالب):

template <int S>
class A
{
  char s_[S];
public:
  A()
  {
    for(int i = 0; i < S; ++i)
    {
      s_[i] = 'A';
    }
  }
  int MaxLength() const
  {
    return S;
  }
};

extern void useA(A<5> &a, int n); // fool the optimizer into generating any code at all

void test()
{
  A<5> a5;
  useA(a5, a5.MaxLength());
}

إخراج المجمع هو ما يلي:

test PROC

[snip]

; 25   :    A<5> a5;

mov eax, 41414141H              ;"AAAA"
mov DWORD PTR a5[esp+40], eax
mov BYTE PTR a5[esp+44], al

; 26   :    useA(a5, a5.MaxLength());

lea eax, DWORD PTR a5[esp+40]
push    5               ; MaxLength()
push    eax
call    useA

نعم هو كذلك ليس الحصول على أي كفاءة أكثر من ذلك. التوقف عن القلق والثقة بمبرمك أو على الأقل إلقاء نظرة على ما ينتج عنه برنامج التحويل البرمجي الخاص بك قبل محاولة إيجاد طرق للتحسين. للمقارنة أنا أيضا جمع الرمز باستخدام std::fill(s_, s_ + S, 'A') و std::memset(s_, 'A', S) بدلا من حلقة الحلقة والتركيبات أنتجت الإخراج المطابق.

إذا كنت على PowerPC، _DCBZ ().

هناك عدد من المواقف حيث سيكون من المفيد الحصول على وظيفة "memspread" التي كان سلوكها المحدد لنسخ جزء البداية من نطاق الذاكرة في كل شيء كله. على الرغم من أن memets () لا بأس به فقط إذا كان الهدف هو نشر قيمة بايت واحدة، فهناك أوقات قد ترغب في ملء صفيف من الأعداد الصحيحة بنفس القيمة. في العديد من تطبيقات المعالجات، فإن نسخ البايت في وقت واحد من المصدر إلى الوجهة سيكون وسيلة فنية جميلة لتنفيذها، لكن وظيفة مصممة جيدا يمكن أن تسفر عن نتائج جيدة. على سبيل المثال، ابدأ برؤية ما إذا كان مقدار البيانات أقل من 32 بايت أو نحو ذلك؛ إذا كان الأمر كذلك، فقط قم بنسخ نسخ Bytewise؛ تحقق خلاف ذلك محاذاة المصدر والوجهة؛ إذا كانت محاذاة، في جولة الحجم إلى أقرب كلمة (إذا لزم الأمر)، فقم بنسخ الكلمة الأولى في كل مكان يذهب إليها، انسخ الكلمة التالية في كل مكان يذهب، إلخ.

أنا أيضا أتمنى لك في بعض الأحيان لوظيفة تم تحديدها للعمل كنسخة من أسفل إلى أعلى، مقصود للاستخدام مع النطاقات المتداخلة. أما لماذا لا يوجد معيار واحد، أعتقد أنه لا أحد يعتقد أنه مهم.

memcpy() يجب أن يكون هذا السلوك. memmove() لا يتم التصميم، إذا تداخل كتل الذاكرة، فسيتم نسخ المحتويات بدءا من طرفي المخازن المؤقتة لتجنب هذا النوع من السلوك. ولكن لملء المخزن المؤقت بقيمة محددة يجب أن تستخدمها memset() في ج أو std::fill() في C ++، ما الذي سيؤدي معظم المحامرة الحديثة إلى تحسين تعليمات ملء الكتلة المناسبة (مثل REP Stosb على بنية X86).

كما قيل من قبل، تقدم ميمتس () الوظيفة المطلوبة.

MEMCPY () هو أن تتحرك حول كتل الذاكرة في جميع الحالات التي لا تتداخل فيها المصدر والمخازن المؤقتة للوجهة، أو عندما dest <مصدر.

MemMOVE () يحل حالة المخازن المؤقتة المتداخلة ومدرسة / المصدر.

على هياكل X86، تحل محكمات جيدة استبدال مكالمات ميمزة مباشرة مع إرشادات التجميع المضمنة بفعالية للغاية بفعالية للغاية ذاكرة المخزن المؤقت للوجهة، حتى تطبيق تحسينات إضافية مثل استخدام قيم 4 بايت لملء أطول فترة ممكنة (إذا كان الكود التالي غير صحيح إلقاء اللوم على عدم استخدام رمز التجميع X86 لفترة طويلة):

lea edi,dest
;copy the fill byte to all 4 bytes of eax
mov al,fill
mov ah,al
mov dx,ax
shl eax,16
mov ax,dx
mov ecx,count
mov edx,ecx
shr ecx,2
cld
rep stosd
test edx,2
jz moveByte
stosw
moveByte:
test edx,1
jz fillDone
stosb
fillDone:

في الواقع هذا الرمز هو أكثر كفاءة بكثير من إصدار Z80 الخاص بك، لأنه لا يؤدي الذاكرة إلى الذاكرة، ولكن التسجيل فقط على تحركات الذاكرة. رمز Z80 الخاص بك هو في الواقع اختراق تماما لأنه يعتمد على كل عملية نسخ ملء مصدر النسخة اللاحقة.

إذا كان برنامج التحويل البرمجي في منتصف الطريق، فقد يكون قادرا على اكتشاف رمز C ++ أكثر تعقيدا يمكن تقسيمه إلى Memets (انظر النشر أدناه)، لكنني أشك في أن هذا يحدث بالفعل لحلقات متداخلة، وربما استدعاء وظائف التهيئة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top