مقاله فنی

کش دیسک نمایشگر PDF و DPI به ازای هر مانیتور در دلفی

دو شکایت همیشه همراه هر نمایشگر PDF جاسازی شده است: اسناد بزرگ در هر بار اجرا مجدداً از صفر رندر می‌شوند و متن به محض کشیده شدن پنجره به مانیتور با DPI بالا، تار می‌شود. کتابخانه PDF losLab هر دو مشکل را در کنترل TPDFlibViewer خود حل کرده است: ویژگی DiskCacheFolder یک کش صفحه دیسک بین-نشستی را که با اثر انگشت سند کلیدگذاری شده فعال می‌کند، و ویژگی ScreenDPI که با پشتیبانی از GetDpiForWindow و یک هندلر WM_DPICHANGED کار می‌کند، به کنترل آگاهی واقعی DPI به ازای هر مانیتور را می‌دهد

هر دو مشکل ارزش جدی گرفتن را دارند زیرا کاربران فوراً متوجه آنها می‌شوند. یک قرارداد اسکن شده ۴۰۰ صفحه‌ای که دیروز هشت ثانیه طول کشید تا قابل اسکرول شود، امروز نیز دوباره هشت ثانیه طول می‌کشد، حتی اگر هیچ چیز در فایل تغییر نکرده باشد. و در یک دسکتاپ با DPIهای مخلوط، نمایشگری که با DPI ۹۶ مانیتور اصلی رندر می‌شود، در آنجا خوب به نظر می‌رسد و روی پنل 4K کناری آن به وضوح تار دیده می‌شود. این مقاله بررسی می‌کند که چگونه TPDFlibViewer هر کدام را حل می‌کند و مفیدتر از آن، چرا طراحی به این شکل انجام شده است. اگر با خود کنترل آشنایی ندارید، ابتدا با بررسی اجمالی کنترل نمایشگر تعاملی PDF شروع کنید و سپس برای لایه عملکرد به اینجا برگردید

چگونه صفحات رندر شده PDF را در بین نشست‌ها در دلفی کش کنیم؟

یک پوشه را به TPDFlibViewer.DiskCacheFolder اختصاص دهید و کنترل، بیت‌مپ‌های صفحات رندر شده را روی دیسک ذخیره می‌کند، بنابراین سندی که اخیراً مشاهده شده است به جای رندر مجدد، بلافاصله باز می‌شود. در دیسک ساختار به این صورت است: <Folder>\PDFlibPas-PageCache\<document fingerprint>\p<page>_z<zoomkey>.bin: یک زیرپوشه برای هر سند، یک فایل برای هر ترکیب صفحه و زوم. بقیه کارها خودکار است؛ هیچ API کشی برای فراخوانی وجود ندارد و چیزی برای پاک کردن وجود ندارد

شمارنده DiskCacheHits گزارش می‌دهد که کش دیسک چند بار رندر صفحه را در نشست فعلی ذخیره کرده است، که این دستاورد را به جای اینکه حدسی باشد، قابل اندازه‌گیری می‌کند. در سندی که بسته و دوباره باز کرده‌اید، هر صفحه‌ای که بدون اجرای مرحله رندر به نمای دید اسکرول می‌شود، این شمارنده را افزایش می‌دهد

چرا کلید کش یک اثر انگشت است و نه یک شماره نسخه

اثر انگشت سند یک هش ۶۴ بیتی FNV-1a روی رشته path|size|last write time است که به صورت شانزده رقم هگزادسیمال فرمت‌دهی شده است. این سه فاکتور کل استراتژی باطل‌سازی است: هنگامی که فایل ویرایش می‌شود، اندازه یا زمان نوشتن آن تغییر می‌کند، هش تغییر می‌کند و نمایشگر به سادگی یک زیرپوشه کش متفاوت را باز می‌کند. پوشه قدیمی دیگر هرگز مورد مراجعه قرار نمی‌گیرد و در نهایت به دلیل هرس LRU حذف می‌شود. هیچ پروتکل تگ نسخه‌ای برای نگهداری وجود ندارد، هیچ منطق مقایسه زمان‌سنجی که دچار انحراف شود وجود ندارد، و هیچ راهی برای ارائه یک بیت‌مپ قدیمی برای یک سند اصلاح‌شده وجود ندارد

این همان ایده "شناسه محتوا به عنوان کلید کش" است که سیستم‌های بیلد استفاده می‌کنند و ارزش خود را در حذف حالت‌های خرابی نشان می‌دهد. کشی که فایل‌ها را تحت نام ساده سند ذخیره می‌کند، باید ویرایش‌ها را به طور صریح شناسایی کند، و هر بررسی صریح، باگی است که منتظر یک مورد خاص است: فایلی که از بک‌آپ با یک مهر زمانی قدیمی بازیابی شده است، ذخیره‌ای که اندازه را حفظ می‌کند، یا مسیری که در یک دستگاه به کوچک و بزرگی حروف حساس است و در دستگاه دیگر نیست. هش کردن هر سه سیگنال در نام پوشه، کهنگی اطلاعات را از نظر ساختاری غیرممکن می‌کند، به جای اینکه به صورت فرآیندی بررسی شود

چرا چرخش به طور تعمدی از کلید دیسک خارج شده است

نام فایل کش شماره صفحه و زوم را کدگذاری می‌کند و نه چیز دیگری — زاویه چرخش نما به طور عمدی وجود ندارد. کش دیسک همیشه بایت‌های رندر شده بدون چرخش را ذخیره می‌کند؛ وقتی یک صفحه کش شده در حالی که نما چرخیده است بارگذاری می‌شود، پیکسل‌ها پس از رمزگشایی در حافظه مجدداً جهت‌دهی می‌شوند. اگر چرخش بخشی از کلید روی دیسک بود، کاربری که چهار زاویه نما را طی می‌کند، همان صفحه را چهار بار روی دیسک می‌نوشت که رشد کش را برای اطلاعات اضافی صفر، چهار برابر می‌کرد، زیرا چرخش در مقایسه با یک مرحله رندر کامل، یک جابجایی ارزان پیکسل است

کش بیت‌مپ درون حافظه از یک کلید ترکیبی استفاده می‌کند، Round(Zoom * 1000) * 4 + Rotation div 90، زیرا در آنجا بیت‌مپ چرخیده دقیقاً همان چیزی است که هندلر رسم نیاز دارد. ترد پیش‌واکشی پس‌زمینه (prefetch thread) نیز از همان قانون پیروی می‌کند: همیشه صفحات بدون چرخش را رندر می‌کند و کلیدهای زوم ساده را گزارش می‌دهد، و ترد اصلی هنگام دریافت نتایج، چرخش را اعمال می‌کند — همان قانون "مالکیت ترد اصلی بر کش" که در رندر موازی صفحه و ایمنی ترد بحث شد. دور نگه داشتن چرخش از هر چیزی زیر لایه حافظه به این معنی است که یک رندر استاندارد به ازای هر صفحه و زوم در هر جایی که ذخیره‌سازی آن پرهزینه است وجود دارد

هرس LRU چگونه پوشه کش را محدود نگه می‌دارد؟

کش تا ده پوشه سند را نگه می‌دارد و هنگام ورود اسناد جدید، مواردی را که اخیراً کمتر استفاده شده‌اند هرس می‌کند. استفاده اخیر توسط یک فایل last-used.marker در هر پوشه سند ردیابی می‌شود که زمان آن در هر دو عملیات نوشتن و خواندن کش به‌روز می‌شود، بنابراین سندی که شما فقط دوباره می‌خوانید بدون نیاز به نوشتن مجدد، فعال باقی می‌ماند. پوشه متعلق به سند در حال حاضر باز از حذف معاف است، بنابراین هرس هرگز نمی‌تواند صفحات را از زیر نمای فعال خارج کند

هر عملیات دیسک در کش — اثر انگشت، ایجاد پوشه، به‌روزرسانی نشانگر، خواندن و نوشتن صفحه، هرس — استثناهای خود را نادیده می‌گیرد. این یک رویکرد عمدی است، نه شلختگی: کش یک شتاب‌دهنده مبتنی بر تلاش است، و یک دیسک پر، یک پوشه فقط‌خواندنی یا قفل آنتی‌ویروس باید عملکرد نمایشگر را به "رندر مانند قبل" کاهش دهد، نه به "عدم نمایش صفحه". بدترین نتیجه‌ای که یک خطای IO کش می‌تواند ایجاد کند، از دست رفتن کش (cache miss) است

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // How many render passes did the disk cache save this session?
  Log(Format('Disk cache hits: %d', [FViewer.DiskCacheHits]));
end;

چگونه یک نمایشگر PDF را در دلفی نسبت به DPI آگاه کنیم؟

کامپوننت TPDFlibViewer هر محاسبه وابسته به DPI را از طریق یک فیلد واحد هدایت می‌کند و این طراحی نقطه-تغییر-واحد چیزی است که پشتیبانی به ازای هر مانیتور را آسان می‌کند. کدهای چیدمان، پیش‌واکشی و رندر همگی مقدار یک فیلد FScreenDPI را می‌خوانند؛ در زوم ۱۰۰٪، یک نقطه PDF به پیکسل‌های ScreenDPI / 72 نگاشت می‌شود. ستتر ویژگی عمومی ScreenDPI کش بیت‌مپ را پاک می‌کند، چیدمان را دوباره می‌سازد، محدوده‌های اسکرول را به‌روز می‌کند و پنجره را نامعتبر می‌کند، بنابراین تغییر DPI یک تخصیص ساده است که هر مصرف‌کننده‌ای آن را به طور خودکار دنبال می‌کند. همین ریاضیات پیکسل هنگام رندر برای چاپگر به جای مانیتور نیز اهمیت دارد، موضوعی که در پیش‌نمایش چاپ و رندر متن دستگاه پوشش داده شده است

شناسایی و رفتار پرش DPI

شناسایی به جای سازنده در بازنویسی (override) متد CreateWnd اتفاق می‌افتد، زیرا GetDpiForWindow به یک هندل پنجره معتبر نیاز دارد و سازنده مدت‌ها قبل از وجود آن اجرا می‌شود. تا زمانی که CreateWnd فراخوانی شود، کنترل مقدار پیش‌فرض قدیمی ۹۶ را نگه می‌دارد تا ریاضیات چیدمان منطقی بماند. خود GetDpiForWindow یک API ویندوز 10 1607+ است، بنابراین کنترل آن را به صورت پویا با GetProcAddress متصل می‌کند و در سیستم‌های قدیمی‌تر به GetDeviceCaps(LOGPIXELSY) بازمی‌گردد — یونیت همچنان در همه جا بارگذاری می‌شود و ویندوزهای قدیمی‌تر به سادگی DPI سیستم را گزارش می‌دهند

وقتی پنجره به مانیتور 4K منتقل می‌شود چه اتفاقی می‌افتد؟

ویندوز زمانی که یک پنجره آگاه به مانیتور به مانیتوری با ضریب مقیاس متفاوت منتقل می‌شود، پیام WM_DPICHANGED را ارسال می‌کند و TPDFlibViewer آن را به طور مستقیم مدیریت می‌کند. هندلر، DPI جدید را از کلمه پایین wParam می‌خواند، آن را از طریق SetScreenDPI اختصاص می‌دهد — که کش بیت‌مپ نامعتبر را حذف کرده و صفحات را مجدداً چیدمان می‌کند — و سپس مستطیل پنجره پیشنهادی را که ویندوز در lParam ارسال می‌کند از طریق SetWindowPos می‌پذیرد که اندازه کلاینت منطقی را در طول انتقال ثابت نگه می‌دارد. کاربر می‌بیند که سند به جای یک بیت‌مپ کشیده شده توسط سیستم‌عامل، با مقیاس جدید به طور شفاف رندر می‌شود

// Normally you never touch ScreenDPI: CreateWnd detects the host monitor
// and WM_DPICHANGED tracks moves. Override it only for special targets,
// e.g. rendering the layout as if for a 150% display:
FViewer.ScreenDPI := 144;  // clears the bitmap cache and rebuilds layout

محدودیت‌هایی که ارزش دانستن دارند قبل از اینکه محصول را ارائه دهید

سه محدودیت عملی شایسته جایگاهی در بررسی طراحی شما هستند. اول اینکه WM_DPICHANGED تنها در صورتی می‌رسد که فرآیند (process) این کار را انتخاب کرده باشد: مانیفست برنامه شما باید آگاهی DPI به ازای هر مانیتور (per-monitor v2 در ویندوزهای فعلی) را اعلام کند، که در Delphi IDE تنظیم آگاهی DPI در بخش گزینه‌های Application است. یک فرآیند آگاه به DPI سیستم هرگز این پیام را دریافت نمی‌کند و کنترل سپس با DPI که در زمان ایجاد پنجره شناسایی کرده است رندر می‌شود. دوم اینکه کش دیسک، فضای دیسک را با سرعت معامله می‌کند — بیت‌مپ‌های صفحات ده سند در چندین سطح زوم، فضای ذخیره‌سازی واقعی را اشغال می‌کند، بنابراین DiskCacheFolder را به مکانی اشاره دهید که مشکلی با رشد آن ندارید، نه یک پروفایل رومینگ (roaming). سوم اینکه پوشه کش نیاز به مجوز نوشتن برای کاربر در حال اجرا دارد؛ بدون آن نمایشگر همچنان کار می‌کند، در سکوت و با صفر موفقیت (hit) — اگر مشکوک هستید که کش فعال نیست، DiskCacheHits را در تست بررسی کنید

این دو ویژگی در کنار هم، کیفیت درک‌شده یک برنامه پر از سند را بیش از اکثر بهینه‌سازی‌های رندر تغییر می‌دهند: اسنادی که کاربران شما دوباره به آنها مراجعه می‌کنند فوراً باز می‌شوند و نما روی هر مانیتوری که قرار گیرد شفاف باقی می‌ماند. هر دو ویژگی به عنوان ویژگی‌های معمولی روی TPDFlibViewer در losLab PDF Library در دسترس هستند، بنابراین اتخاذ آنها به جای یک پروژه موتور رندرینگ، یک بعدازظهر سیم‌کشی است