מאת: Peter Mandl, Udeepta Bordoloi, Advanced Micro Devices, Inc
המעבדים הגרפיים של ימינו הם מנועי מחשוב מקבילי הפועלים באופן נרחב ביותר, שניתנים לתכנות במידה גבוהה. עם פיתוחן של שפות תכנות מקביליות פתוחות בתקנים תעשייתיים, כמו למשל OpenCLTM, ועם ההתפתחות הנמשכת של המחשוב ההטרוגני, מציעות היחידות לעיבוד גרפי רב תכליתיות (GPGPU) יכולות מלהיבות חדשות עבור שוק ההתקנים המשובצים.
האתגרים העומדים בפני יחידות GPGPU
מה שהופך את המחשוב באמצעות יחידות GPGPU לאטרקטיבי כל כך היא זמינותם של הביצועים המצוינים בנקודה צפה ביחידות העיבוד הגרפי הכדאיות מבחינת העלות. על מנת להפיק תועלת מהשיפור בביצועים, חייבות המערכות המשובצות לעמוד בשלושה אתגרים נוספים: צריכת הספק נמוכה יותר, תקנים פתוחים ואלגוריתמים מקביליים.
צריכת הספק נמוכה היא אחד האתגרים האלו, מאחר שליישומים משובצים יש באופן אופייני נקודות סף צנועות יותר ביחס להספק הכולל הנצרך (TDP). כשבנוסף ניצבים אתגרי מגבלות נוספים בתחום הגודל, המשקל וההספק (SWaP). למשל, מכשירי על-קול (אולטרה-סאונד) ניידים נהנים מהיתרון של גודל קטן, אך נדרשות מהם יכולות מחשוב וביצועים גבוהים עבור דימות בזמן אמת. יחידות GPGPU מציעות יכולות מחשוב חדשות בתוך תקציבי ההספק המוגבלים שמוקצים לתשתיות תקשורת. לרבים מבין היישומים הצבאיים והחלל-תעופה (כמו למשל גלאי הד – סונאר, מכ”מ, מערכות מעקב בווידיאו) נדרשות יכולות מחשוב וביצועים גבוהים המסופקים בממדים של מערכות משובצות. כדי לעמוד בדרישה הגוברת ליחידות GPGPU משובצות, יחידת העיבוד הגרפי המשובצת Radeon E6760 של AMD מספקת 16.5 ג’יגה-FLOP לוואט בצריכת TDP צנועה של בערך 35 וואט.
מחשוב הטרוגני
ו–OpenCL
על מנת לקבל גישה ליתרונות הביצועים האלו, מספקת OpenCLTM תקן פתוח אשר מאפשר ביצוע תכנות מקבילי של יחידות עיבוד גרפיות (ושל מעבדים נוספים כמו למשל CPU, התקני FPGA, Cell וכיו”ב). התקן נוצר על ידי מאגד (קונסורציום) שכולל יצרני שבבים רבים, חברות תוכנה וארגוני מחקר. עם התייצבותו כתקן בשל, הוא הפך לממשק API הנבחר עבור קוד שאפשר לנייד אותו בין סוגי חומרה שונים וגם בין מערכות הפעלה שונות. סעיף זה דן באופן שבו אפשר להשתמש ב- OpenCLTM לתכנות יחידות עיבוד גרפיות.
הגרעין של OpenCL
יחידות עיבוד גרפיות פועלות מצוין בעיבוד מקבילי, במיוחד בביצוע של חישובים זהים בכמויות גדולות של נתונים (אשר מכונה גם חישובים מקביליים בנתונים). כאן נעשה שימוש בדוגמה פשוטה ביותר על מנת להדגים את הנקודה הזו, באמצעות חיבור פשוט של שני מערכים, a ו-b מבחינת האברים שלהם, כאשר את התוצאה יש לכתוב למערך נוסף, c. במקום לחבר זוג אברים בכל פעם, כפי שמתרחש בקוד של CPU, נוכל להשתמש בתקן OpenCLTM על מנת לבצע פעולות חיבור רבות במקביל ביחידת העיבוד הגרפי. הטבלה המופיעה בהמשך היא פיסת קוד אופיינית המיועדת לביצוע החיבור באמצעות CPU בעל ליבה אחת, והיא נראית דומה מאוד לגרעין OpenCLTM אשר יבצע אותו חיבור ביחידת עיבוד גרפי.
הפעולה על כל פריט מכונה פריט-עבודה. מבחינת התפישה, כל פריטי-העבודה מבוצעים בגרעין במקביל – אין אפשרות לדעת האם פריט-העבודה i=x מבוצע לפני פריט עבודה אחר כלשהו i=y, באותה העת עמו או לאחריו. (עם זאת, אנו יודעים שביחידות עיבוד גרפי יכולים מאות פריטי עבודה, או אף אלפי פריטי עבודה, להימצא בכל רגע ורגע תוך כדי ביצועם.)
תקן OpenCLTM מספק לנו דרך לקבץ יחדיו אצוות של פריטי-עבודה לקבוצות-עבודה. באופן כללי, אין אפשרות ליצור סנכרון בין פריטי-עבודה או לאפשר העברת נתונים ביניהם, אך פעולות אלו אפשריות בין פריטי-עבודה השייכים לאותה קבוצת–עבודה. כך מתאפשר לנו לכתוב גרעיני OpenCLTM, שהם מתוחכמים יותר בהרבה מהדוגמה הזו. דוגמאות מתקדמות יותר אינן כלולות בהיקף של מאמר זה, ועם זאת, אפשר למצוא ברשת דפי הדרכה רבים אשר מציגים שימושים מתקדמים בתכונות הגרעין של OpenCLTM.
תכנית יחידת המארח של OpebCL
עד כה דיברנו על הקוד שיתבצע ביחידת עיבוד גרפי. עם זאת, יהיה עלינו לכתוב תוכנית יחידת מארח (קוד ל-CPU) על מנת לבקר את השימוש ביחידת העיבוד הגרפי. התוכנית תאתר את יחידת או את יחידות העיבוד הגרפי, תאתחל אותן ותשלח את הנתונים ואת קוד הגרעין אל יחידת GPU, תעביר ליחידת GPU פקודה להתחיל בביצוע, תקבל מידע לגבי המועד שבו התוצאות תהיינה מוכנות, ולבסוף היא תקרא אותם מיחידת העיבוד הגרפי.
תקן OpenCLTM מאפשר למימושים מרובים של OpenCLTM להתקיים זה בצד זה באותו המחשב. לדוגמה, יכולים להתקיים מימוש שפעולתו מכוונת ל-CPU, מימוש נוסף עבור יחידות העיבוד הגרפי ועוד מימוש נוסף למאיצים אחרים, שאולי נמצאים על המעגל. לכן, הצעד הראשון בתוכנית OpenCLTM חייב להיות בדיקה לגילוי מספר הפלטפורמות (המימושים) הקיימות במחשב הנוכחי ובחירה באחת מהן.
בתוך פלטפורמה יש ליצור הקשר (בדרך כלל הקשר אחד, אם כי אפשר ליצור הקשרים נוספים לשימוש מתקדם יותר). אפשר לחשוב על ההקשר כעל יקום עצמאי במידה מסוימת אשר מכיל את שאר משאבי OpenCLTM. כל הצעדים הבאים יתבצעו באופן ייחודי להקשר שאנו יוצרים.
בשלב הבא נתשאל את התקני OpenCLTM הקיימים בהקשר האמור ונאחזר אותם – במקרה זה, את יחידות העיבוד הגרפי. כמו כן יהיה צורך להדר (לבצע קומפילציה) את תוכנית OpenCLTM ולבנות אותה (גרעין אחד או יותר) כדי לבצע אותה בהתקנים שנבחרו.
כמו כן, יש צורך ליצור מאגרי זיכרון זמניים של OpenCLTM אשר יאחסנו את הנתונים לפני הביצוע ביחידת העיבוד גרפי ולאחריו. עבור דוגמת הגרעין לביצוע חיבור, ניצור שלושה מאגרי זיכרון זמניים, ob, oa ו-oc התואמים למערכים b, a ו-c.
עתה, לאחר שביצענו אתחול של יחידת העיבוד הגרפי ושל הקוד שיבוצע ביחידה זו, אפשר לשלוח פקודות ליחידת העיבוד הגרפי (שלח נתונים, בצע גרעין, אחזר תוצאות וכיו”ב). מסיבות של שיפור היעילות, הגיוני שתוכנית יחידת המארח (ה-CPU) תפיק את קריאות הפקודה האלו בצורה א-סנכרונית (ללא חסימה). כלומר, צריך שתוכנית יחידת המארח תוכל להעביר פקודה ליחידת העיבוד הגרפי (לדוגמה, העברת נתונים) ושהיא לא תצטרך להמתין להשלמת ביצוע הפעולה (העברת הנתונים). באופן כזה, יחידת המארח יכולה לעבור לביצוע פעולות נוספות, כולל משלוח של פקודות נוספות ליחידת העיבוד הגרפי.
תקן OpenCLTM מספק מבנה מתאים להעברת פקודות אל יחידת העיבוד הגרפי: תור של פקודות. להקלת השימוש, אפשר למצוא משתנים חוסמים ומשתנים ללא חסימה, על מנת לאפשר שימוש במגוון פקודות שאותן אפשר להעביר לתור הפקודות. בנוסף, קיימים מנגנונים לביצוע שאילתות כדי לבדוק אם ביצוע של פקודה (או אירוע) הושלם, וכן דרכים שבהן תוכנית יחידת המארח יכולה להימצא במצב המתנה עד אשר יסתיים ביצוען של כל פקודות OpenCLTM הקודמות. יש אפשרות לבצע את הפקודות המועברות בסדר שבו הן נמסרו או בסדר אחר, בתלות בתכונות של תור הפקודות.
כל הצעדים שבוצעו עד כה בתוכנית יחידת המארח הן פעולות אתחול המיועדות להגדרת יחידת העיבוד הגרפי, והכנה של יחידה כזו לביצוע תוכנית אחת או יותר. איתרנו פלטפורמה, יצרנו הקשר, אתחלנו את ההתקן וכמה גרעינים נוספים, ויצרנו מאגרי זיכרון זמניים ותור ביצוע פקודות. עבור רוב מקרי השימוש, יש צורך לבצע את הצעדים האלו פעם אחת בלבד, ולאחר מכן אפשר להתקדם לביצוע הקוד ביחידת העיבוד הגרפי.
ראשית יש צורך לשלוח את הנתונים שבמערכים a ו-b אל מאגרי הזיכרון הזמניים לפי OpenCLTM אשר יצרנו קודם. ליתר דיוק, נשלח פקודות (או נעביר אותן לתור) על מנת להתחיל בהעברת הנתונים אל תור הפקודות. שנית, מגדירים את הארגומנטים שבהם ישתמש OpenCLTM בעת הביצוע. עבור הדוגמה המשתמשת בגרעין החיבור נקשר את מאגרי הזיכרון הזמניים ob, oa ו-oc אל פרמטרי הגרעין b, a ו-c.
לאחר מכן, יש להעביר את הפעולה לביצוע הגרעין. יחידת מידע חיונית ביותר שיש למסור בנקודה זו היא מספר הפריטים שעליהם צריך לעבוד גרעין החיבור. כלומר, יש לספק את המספר הכולל של פריטי-העבודה. בדוגמה שכאן, הגודל של המערכים הוא sizeOfArray, כך שזה המספר הכולל של פריטי-העבודה. אחרי העברת הגרעין, יש להעביר פקודה לקרוא נתונים ממאגר הזיכרון הזמני oc ולהעבירם למערך c. בהנחה שהפקודות הקודמות הועברו לתור של הפקודות באופן לא חוסם (ללא חסימה), יש צורך לבצע קריאה ולהמתין עד שכל הפעולות שהועברו בתור של הפקודות הסתיימו, ובנקודה זו התוצאות הנכונות יהיו במערך המטרה c.
מסקנות
ההבנה של תקן OpenCLTM חשובה ביותר לאימוץ נרחב של השימוש בטכנולוגיות של יחידות GPGPU בשוק המערכות המשובצות. אלגוריתמים רבים ממופים היטב לארכיטקטורות של יחידות GPGPU ומדגימים יתרונות ביצועים טובים מאוד בהשוואה למימושי CPU מסורתיים בעלי ריבוי ליבות. יחידות GPGPU מספקות פעולות GFLOP כדאיות לכל וואט ביחס לעלות, ומאפשרות מימוש יכולות חדשות עבור יישומים שלהם יש מגבלות מבחינת הגודל, המשקל וההספק.