dotnet X11 呼叫 XRootWindow 是否耗時

lindexi發表於2024-06-29

本文將透過閱讀 lib x11 程式碼告訴大家,呼叫 XRootWindow 函式是不耗時的,沒有成本的

在我閱讀 Avalonia 和 CPF 和 UNO 框架的程式碼的時候,我發現了很多時候都是在需要用到 RootWindow 時,呼叫 XRootWindow 或 XDefaultRootWindow 獲取 RootWindow 的值。此時我想著是否將 RootWindow 存放起來,這樣可以稍微提升一點效能

在對某個函式呼叫進行效能測量考慮時,不僅可以使用基準效能測試工具進行測試,還可以透過閱讀程式碼的方式瞭解實現原理從而瞭解其效能

透過閱讀 lib x11 的程式碼,我發現了 XRootWindow 方法只是從結構體裡面將值取出來,效能損耗其實和自己將 RootWindow 存起來可以認為是等價的

在 Macros.c 檔案的對 XRootWindow 方法的定義程式碼如下

Window XRootWindow (Display *dpy, int scr)
{
    return (RootWindow(dpy,scr));
}

以上程式碼的 RootWindow 是一個宏定義,定義在 Xlib.h 檔案中,程式碼如下

#define RootWindow(dpy, scr) 	(ScreenOfDisplay(dpy,scr)->root)

從以上的程式碼可以看到,實現就是將傳入的 Display 和 screen number 傳入到 ScreenOfDisplay 裡面。最後取出來一個 root 欄位

再繼續看看 ScreenOfDisplay 這個宏的定義,程式碼如下

#define ScreenOfDisplay(dpy, scr)(&((_XPrivDisplay)(dpy))->screens[scr])

可以看到實現的邏輯十分簡單,那就是將傳入的 Display 轉換為 _XPrivDisplay 結構體型別。接著獲取其 screens 欄位,這個欄位是一個陣列,或者準確說是一個指向 Screen 陣列型別的指標。再取其第 scr 項,也就是取傳入的 screen number 項

回到咱 C# 程式碼,如以下的程式碼定義

var display = XOpenDisplay(IntPtr.Zero);
var screen = XDefaultScreen(display);

以上程式碼拿到的 display 就是以上的 Display 的值,也就是實際的 _XPrivDisplay 結構體型別指標。以上的 screen 準確來說是 screen number 的意思,大部分情況下返回 0 的值

在咱 C# 程式碼呼叫 XRootWindow 方法時,如以下程式碼,其實等同於在 display 裡面先取 Screen 再取其 root 欄位

var xRootWindow = XRootWindow(display, screen);

那這麼說是否可以繞過 XRootWindow 方法,直接不安全使用 _XPrivDisplay 結構體型別指標獲取 RootWindow 內容?答案是可以的

開始之前必須說明的是,這樣的方式是不安全的,強依賴 xlib 的實現。好在這部分邏輯好久都沒有變更了,大概在你的裝置上,我以下的程式碼也能跑起來

先閱讀 _XPrivDisplay 結構體,大概程式碼如下

typedef struct
   #ifdef XLIB_ILLEGAL_ACCESS
   _XDisplay
   #endif
   {
    	XExtData *ext_data;	/* hook for extension to hang data * /
    	struct _XPrivate *private1;
    	int fd;			/* Network socket. * /
    	int private2;
    	int proto_major_version;/* major version of server's X protocol * /
    	int proto_minor_version;/* minor version of servers X protocol * /
    	char *vendor;		/* vendor of the server hardware * /
            XID private3;
    	XID private4;
    	XID private5;
    	int private6;
    	XID (*resource_alloc)(	/* allocator function * /
   		struct _XDisplay*
    	);
    	int byte_order;		/* screen byte order, LSBFirst, MSBFirst * /
    	int bitmap_unit;	/* padding and data requirements * /
    	int bitmap_pad;		/* padding requirements on bitmaps * /
    	int bitmap_bit_order;	/* LeastSignificant or MostSignificant * /
    	int nformats;		/* number of pixmap formats in list * /
    	ScreenFormat *pixmap_format;	/* pixmap format list * /
    	int private8;
    	int release;		/* release of the server * /
    	struct _XPrivate *private9, *private10;
    	int qlen;		/* Length of input event queue * /
    	unsigned long last_request_read; /* seq number of last event read * /
    	unsigned long request;	/* sequence number of last request. * /
    	XPointer private11;
    	XPointer private12;
    	XPointer private13;
    	XPointer private14;
    	unsigned max_request_size; /* maximum number 32 bit words in request* /
    	struct _XrmHashBucketRec *db;
    	int (*private15)(
    		struct _XDisplay*
    		);
    	char *display_name;	/* "host:display" string used on this connect* /
    	int default_screen;	/* default screen for operations * /
    	int nscreens;		/* number of screens on this server* /
    	Screen *screens;	/* pointer to list of screens * /
    	unsigned long motion_buffer;	/* size of motion buffer * /
    	unsigned long private16;
    	int min_keycode;	/* minimum defined keycode * /
    	int max_keycode;	/* maximum defined keycode * /
    	XPointer private17;
    	XPointer private18;
    	int private19;
    	char *xdefaults;	/* contents of defaults from server * /
    	/* there is more to this structure, but it is private to Xlib * /
   }
   #ifdef XLIB_ILLEGAL_ACCESS
   Display,
   #endif
   *_XPrivDisplay;

如上文,咱核心需要的就是拿到 Screen *screens; /* pointer to list of screens * / 欄位的內容

繼續先看看 Screen 的定義,程式碼如下

typedef struct {
   	XExtData *ext_data;	/* hook for extension to hang data * /
   	struct _XDisplay *display;/* back pointer to display structure * /
   	Window root;		/* Root window id. * /
   	int width, height;	/* width and height of screen * /
   	int mwidth, mheight;	/* width and height of  in millimeters * /
   	int ndepths;		/* number of depths possible * /
   	Depth *depths;		/* list of allowable depths on the screen * /
   	int root_depth;		/* bits per pixel * /
   	Visual *root_visual;	/* root visual * /
   	GC default_gc;		/* GC for the root root visual * /
   	Colormap cmap;		/* default color map * /
   	unsigned long white_pixel;
   	unsigned long black_pixel;	/* White and Black pixel values * /
   	int max_maps, min_maps;	/* max and min color maps * /
   	int backing_store;	/* Never, WhenMapped, Always * /
   	Bool save_unders;
   	long root_input_mask;	/* initial root input mask * /
   } Screen;

由於咱只想從 _XPrivDisplay 拿到 screens 欄位,於是在 C# 程式碼定義裡面,可以使用 StructLayout 加 Explicit 方式跳過結構體定義,只定義核心的欄位

[StructLayout(LayoutKind.Explicit)]
struct Display
{
    [FieldOffset(228)]
    public int nscreens;

    [FieldOffset(232)]
    public IntPtr Screens;
}

以上程式碼的 [FieldOffset(228)] 是根據上文的 _XPrivDisplay 算到的在 nscreens 欄位在整個結構體裡面的 byte 量,換句話說就是在第幾個 byte 就是 nscreens 欄位

同樣對 Screen 結構體做相同的定義,只定義其中我需要用到的屬性

[StructLayout(LayoutKind.Explicit)]
struct Screen
{
    [FieldOffset(8)]
    public IntPtr _XDisplay;

    [FieldOffset(8 + 8)]
    public int RootWindow;

    [FieldOffset(8 + 8 + 8)]
    public int Width;
    [FieldOffset(8 + 8 + 8 + 4)]
    public int Height;
}

嘗試透過自己定義的結構體和 XRootWindow 獲取 RootWindow 的值,看是否相同,程式碼如下

var display = XOpenDisplay(IntPtr.Zero);
var screen = XDefaultScreen(display);

var xRootWindow = XRootWindow(display, screen);

unsafe
{
    var pDisplay = (Display*)display;

    var firstScreen = pDisplay->Screens;

    Screen* pScreen = (Screen*) firstScreen;

    var rootWindowFromPScreen = pScreen->RootWindow;

    Console.WriteLine($"{rootWindowFromPScreen} xRootWindow={xRootWindow}");
}

嘗試執行以上程式碼,可以在控制檯裡輸出以下內容

1729 xRootWindow=1729

可以看到兩個值是相同的,證明咱這個獲取方式是正確的,也證明了 xlib 實現確實如此

如此也可以證明 XRootWindow 方法獲取是不耗時沒成本的,只是從結構體將值取出而已。不需要自己存著,自己存著和呼叫方法拿從業務角度沒有效能上的差異

那 XDefaultRootWindow 呢?透過閱讀 Macros.c 程式碼,可以發現和 XRootWindow 差不多,程式碼定義如下

Window XRootWindow (Display *dpy, int scr)
{
    return (RootWindow(dpy,scr));
}

Window XDefaultRootWindow (Display *dpy)
{
    return (RootWindow(dpy,DefaultScreen(dpy)));
}

可以看到不同點僅僅只是 XDefaultRootWindow 傳入 RootWindow 的 scr 是從 DefaultScreen 拿的

繼續進入 Macros.c 程式碼,可以看到 DefaultScreen 宏的定義如下

#define DefaultScreen(dpy) 	(((_XPrivDisplay)(dpy))->default_screen)

如以上程式碼可以看到 XDefaultScreen 也只是從 _XPrivDisplay 結構體取出 default_screen 欄位而已,也可以忽略

如此即可瞭解到呼叫 XRootWindow 和 XDefaultRootWindow 都只是從 Display 結構體取出欄位而已,可以在業務端隨意呼叫,沒有成本

本文以上程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 7ad18fcc3b99003e0864e54e1ea6e696909b4b3b

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 7ad18fcc3b99003e0864e54e1ea6e696909b4b3b

獲取程式碼之後,進入 X11/LabajaycolibearLuleacearewearjaykee 資料夾,即可獲取到原始碼

相關文章