LuaJIT 學習(2)—— 使用 FFI 庫的幾個例子

文章目錄

    • 介紹
    • Motivating Example: Calling External C Functions
      • 例子:Lua 中調用 C 函數
    • Motivating Example: Using C Data Structures
    • Accessing Standard System Functions
    • Accessing the zlib Compression Library
    • Defining Metamethods for a C Type
      • 例子:創建 userdata
    • Translating C Idioms
    • To Cache or Not to Cache

介紹

The FFI library allows calling external C functions and using C data structures from pure Lua code.

The FFI library largely obviates the need to write tedious manual Lua/C bindings in C. No need to learn a separate binding language — it parses plain C declarations! These can be cut-n-pasted from C header files or reference manuals. It’s up to the task of binding large libraries without the need for dealing with fragile binding generators.

The FFI library is tightly integrated into LuaJIT (it’s not available as a separate module). The code generated by the JIT-compiler for accesses to C data structures from Lua code is on par with the code a C compiler would generate. Calls to C functions can be inlined in JIT-compiled code, unlike calls to functions bound via the classic Lua/C API.

Motivating Example: Calling External C Functions

It’s really easy to call an external C library function:

-- 1
local ffi = require("ffi")
-- 2
ffi.cdef[[
int printf(const char *fmt, ...);
]]
-- 3
ffi.C.printf("Hello %s!", "world")
  1. Load the FFI library.
  2. Add a C declaration for the function. The part inside the double-brackets is just standard C syntax.
  3. Call the named C function — Yes, it’s that simple!

Actually, what goes on behind the scenes is far from simple: makes use of the standard C library namespace ffi.C. Indexing this namespace with a symbol name ("printf") automatically binds it to the standard C library. The result is a special kind of object which, when called, runs the printf function. The arguments passed to this function are automatically converted from Lua objects to the corresponding C types.

Ok, so maybe the use of printf() wasn’t such a spectacular example. You could have done that with io.write() and string.format(), too. But you get the idea …

So here’s something to pop up a message box on Windows:

local ffi = require("ffi")
ffi.cdef[[
int MessageBoxA(void *w, const char *txt, const char *cap, int type);
]]
ffi.C.MessageBoxA(nil, "Hello world!", "Test", 0)

Bing! Again, that was far too easy, no?

例子:Lua 中調用 C 函數

如果沒有 FFI 庫,調用 C 庫的 printf 函數需要寫一個 C 模塊,然后在 Lua 代碼中調用。

#include <stdio.h>
#include <stdarg.h>
#include <lua.h>
#include <lauxlib.h>// C 函數:調用 printf 并返回打印的字符數
static int l_myprintf(lua_State *L) {const char *format = luaL_checkstring(L, 1); // 獲取格式化字符串const char *str = luaL_checkstring(L, 2);    // 獲取第二個參數int result = printf(format, str); // 調用 printfreturn 0; // 不返回任何 Lua 值
}// Lua 調用 C 函數的映射表
static const struct luaL_Reg mylib[] = {{"printf", l_myprintf},{NULL, NULL} // 結束標記
};// 入口函數,注冊庫
int luaopen_mylib(lua_State *L) {luaL_register(L, "mylib", mylib);return 1;
}

Lua 代碼

local mylib = require("mylib")
mylib.printf("Hello %s!\n", "world")

Compare this with the effort required to bind that function using the classic Lua/C API: create an extra C file, add a C function that retrieves and checks the argument types passed from Lua and calls the actual C function, add a list of module functions and their names, add a luaopen_* function and register all module functions, compile and link it into a shared library (DLL), move it to the proper path, add Lua code that loads the module aaaand … finally call the binding function. Phew!

一對比,調用 C 函數簡單了很多!

再看一個例子,在 Lua 中直接使用 C 語言的結構體。

Motivating Example: Using C Data Structures

The FFI library allows you to create and access C data structures. Of course, the main use for this is for interfacing with C functions. But they can be used stand-alone, too.

Here’s a sketch of a library that operates on color images, plus a simple benchmark. First, the plain Lua version:

local floor = math.floorlocal function image_ramp_green(n)local img = {}local f = 255/(n-1)for i=1,n doimg[i] = { red = 0, green = floor((i-1)*f), blue = 0, alpha = 255 }endreturn img
endlocal function image_to_gray(img, n)for i=1,n dolocal y = floor(0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blue)img[i].red = y; img[i].green = y; img[i].blue = yend
endlocal N = 400*400
local img = image_ramp_green(N)
for i=1,1000 doimage_to_gray(img, N)
end

This creates a table with 160.000 pixels, each of which is a table holding four number values in the range of 0-255. First, an image with a green ramp is created (1D for simplicity), then the image is converted to grayscale 1000 times. Yes, that’s silly, but I was in need of a simple example …

And here’s the FFI version. The modified parts have been marked in bold:

-- 1
local ffi = require("ffi")
ffi.cdef[[
typedef struct { uint8_t red, green, blue, alpha; } rgba_pixel;
]]local function image_ramp_green(n)
-- 2local img = ffi.new("rgba_pixel[?]", n)local f = 255/(n-1)
-- 3for i=0,n-1 do
-- 4img[i].green = i*fimg[i].alpha = 255endreturn img
endlocal function image_to_grey(img, n)
-- 3for i=0,n-1 do
-- 5local y = 0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blueimg[i].red = y; img[i].green = y; img[i].blue = yend
endlocal N = 400*400
local img = image_ramp_green(N)
for i=1,1000 doimage_to_grey(img, N)
end

Ok, so that wasn’t too difficult:

  1. First, load the FFI library and declare the low-level data type. Here we choose a struct which holds four byte fields, one for each component of a 4x8 bit RGBA pixel.
  2. Creating the data structure with ffi.new() is straightforward — the '?' is a placeholder for the number of elements of a variable-length array.
  3. C arrays are zero-based, so the indexes have to run from 0 to n-1. One might want to allocate one more element instead to simplify converting legacy code.
  4. Since ffi.new() zero-fills the array by default, we only need to set the green and the alpha fields.
  5. The calls to math.floor() can be omitted here, because floating-point numbers are already truncated towards zero when converting them to an integer. This happens implicitly when the number is stored in the fields of each pixel.

Now let’s have a look at the impact of the changes: first, memory consumption for the image is down from 22 Megabytes to 640 Kilobytes (4004004 bytes). That’s a factor of 35x less! So, yes, tables do have a noticeable overhead. BTW: The original program would consume 40 Megabytes in plain Lua (on x64).

內存消耗比原始的程序少35倍!

Next, performance: the pure Lua version runs in 9.57 seconds (52.9 seconds with the Lua interpreter) and the FFI version runs in 0.48 seconds on my machine (YMMV). That’s a factor of 20x faster (110x faster than the Lua interpreter).

使用 LuaJIT FFI 庫,執行時間比使用 Lua5.1 的純 Lua 程序快110倍!

知道 LuaJIT 牛,沒想到這么牛!

再看一些例子

Accessing Standard System Functions

The following code explains how to access standard system functions. We slowly print two lines of dots by sleeping for 10 milliseconds after each dot:

local ffi = require("ffi")
-- 1
ffi.cdef[[
void Sleep(int ms);
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]]local sleep
-- 2
if ffi.os == "Windows" then
-- 3function sleep(s)
-- 4ffi.C.Sleep(s*1000)end
elsefunction sleep(s)
-- 5ffi.C.poll(nil, 0, s*1000)end
endfor i=1,160 doio.write("."); io.flush()
-- 6sleep(0.01)
end
io.write("\n")

Here’s the step-by-step explanation:

  1. This defines the C library functions we’re going to use. The part inside the double-brackets is just standard C syntax. You can usually get this info from the C header files or the documentation provided by each C library or C compiler.
  2. The difficulty we’re facing here, is that there are different standards to choose from. Windows has a simple Sleep() function. On other systems there are a variety of functions available to achieve sub-second sleeps, but with no clear consensus. Thankfully poll() can be used for this task, too, and it’s present on most non-Windows systems. The check for ffi.os makes sure we use the Windows-specific function only on Windows systems.
  3. Here we’re wrapping the call to the C function in a Lua function. This isn’t strictly necessary, but it’s helpful to deal with system-specific issues only in one part of the code. The way we’re wrapping it ensures the check for the OS is only done during initialization and not for every call.
  4. A more subtle point is that we defined our sleep() function (for the sake of this example) as taking the number of seconds, but accepting fractional seconds. Multiplying this by 1000 gets us milliseconds, but that still leaves it a Lua number, which is a floating-point value. Alas, the Sleep() function only accepts an integer value. Luckily for us, the FFI library automatically performs the conversion when calling the function (truncating the FP value towards zero, like in C).

Some readers will notice that Sleep() is part of KERNEL32.DLL and is also a stdcall function. So how can this possibly work? The FFI library provides the ffi.C default C library namespace, which allows calling functions from the default set of libraries, like a C compiler would. Also, the FFI library automatically detects stdcall functions, so you don’t need to declare them as such.

  1. The poll() function takes a couple more arguments we’re not going to use. You can simply use nil to pass a NULL pointer and 0 for the nfds parameter. Please note, that the number 0 does not convert to a pointer value, unlike in C++. You really have to pass pointers to pointer arguments and numbers to number arguments.

The page on FFI semantics has all of the gory details about conversions between Lua objects and C types. For the most part you don’t have to deal with this, as it’s performed automatically and it’s carefully designed to bridge the semantic differences between Lua and C.

  1. Now that we have defined our own sleep() function, we can just call it from plain Lua code. That wasn’t so bad, huh? Turning these boring animated dots into a fascinating best-selling game is left as an exercise for the reader. 😃

Accessing the zlib Compression Library

The following code shows how to access the zlib compression library from Lua code. We’ll define two convenience wrapper functions that take a string and compress or uncompress it to another string:

local ffi = require("ffi")
-- 1
ffi.cdef[[
unsigned long compressBound(unsigned long sourceLen);
int compress2(uint8_t *dest, unsigned long *destLen,const uint8_t *source, unsigned long sourceLen, int level);
int uncompress(uint8_t *dest, unsigned long *destLen,const uint8_t *source, unsigned long sourceLen);
]]
-- 2
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z")local function compress(txt)
-- 3local n = zlib.compressBound(#txt)local buf = ffi.new("uint8_t[?]", n)
-- 4local buflen = ffi.new("unsigned long[1]", n)local res = zlib.compress2(buf, buflen, txt, #txt, 9)assert(res == 0)
-- 5return ffi.string(buf, buflen[0])
end-- 6
local function uncompress(comp, n)local buf = ffi.new("uint8_t[?]", n)local buflen = ffi.new("unsigned long[1]", n)local res = zlib.uncompress(buf, buflen, comp, #comp)assert(res == 0)return ffi.string(buf, buflen[0])
end-- 7. Simple test code.
local txt = string.rep("abcd", 1000)
print("Uncompressed size: ", #txt)
local c = compress(txt)
print("Compressed size: ", #c)
local txt2 = uncompress(c, #txt)
assert(txt2 == txt)

Here’s the step-by-step explanation:

  1. This defines some of the C functions provided by zlib. For the sake of this example, some type indirections have been reduced and it uses the predefined fixed-size integer types, while still adhering to the zlib API/ABI.

  2. This loads the zlib shared library. On POSIX systems, it’s named libz.so and usually comes pre-installed. Since ffi.load() automatically adds any missing standard prefixes/suffixes, we can simply load the "z" library. On Windows it’s named zlib1.dll and you’ll have to download it first from the zlib site. The check for ffi.os makes sure we pass the right name to ffi.load().

  3. First, the maximum size of the compression buffer is obtained by calling the zlib.compressBound function with the length of the uncompressed string. The next line allocates a byte buffer of this size. The [?] in the type specification indicates a variable-length array (VLA). The actual number of elements of this array is given as the 2nd argument to ffi.new().

  4. This may look strange at first, but have a look at the declaration of the compress2 function from zlib: the destination length is defined as a pointer! This is because you pass in the maximum buffer size and get back the actual length that was used.

    In C you’d pass in the address of a local variable (&buflen). But since there’s no address-of operator in Lua, we’ll just pass in a one-element array. Conveniently, it can be initialized with the maximum buffer size in one step. Calling the actual zlib.compress2 function is then straightforward.

  5. We want to return the compressed data as a Lua string, so we’ll use ffi.string(). It needs a pointer to the start of the data and the actual length. The length has been returned in the buflen array, so we’ll just get it from there.

Note that since the function returns now, the buf and buflen variables will eventually be garbage collected. This is fine, because ffi.string() has copied the contents to a newly created (interned) Lua string. If you plan to call this function lots of times, consider reusing the buffers and/or handing back the results in buffers instead of strings. This will reduce the overhead for garbage collection and string interning.

  1. The uncompress functions does the exact opposite of the compress function. The compressed data doesn’t include the size of the original string, so this needs to be passed in. Otherwise, no surprises here.
  2. The code, that makes use of the functions we just defined, is just plain Lua code. It doesn’t need to know anything about the LuaJIT FFI — the convenience wrapper functions completely hide it.

One major advantage of the LuaJIT FFI is that you are now able to write those wrappers in Lua. And at a fraction of the time it would cost you to create an extra C module using the Lua/C API. Many of the simpler C functions can probably be used directly from your Lua code, without any wrappers.

Side note: the zlib API uses the long type for passing lengths and sizes around. But all those zlib functions actually only deal with 32 bit values. This is an unfortunate choice for a public API, but may be explained by zlib’s history — we’ll just have to deal with it.

First, you should know that a long is a 64 bit type e.g. on POSIX/x64 systems, but a 32 bit type on Windows/x64 and on 32 bit systems. Thus a long result can be either a plain Lua number or a boxed 64 bit integer cdata object, depending on the target system.

Ok, so the ffi.* functions generally accept cdata objects wherever you’d want to use a number. That’s why we get a away with passing n to ffi.string() above. But other Lua library functions or modules don’t know how to deal with this. So for maximum portability, one needs to use tonumber() on returned long results before passing them on. Otherwise the application might work on some systems, but would fail in a POSIX/x64 environment.

Defining Metamethods for a C Type

The following code explains how to define metamethods for a C type. We define a simple point type and add some operations to it:

local ffi = require("ffi")
-- 1
ffi.cdef[[
typedef struct { double x, y; } point_t;
]]-- 2
local point
local mt = {
-- 3__add = function(a, b) return point(a.x+b.x, a.y+b.y) end,__len = function(a) return math.sqrt(a.x*a.x + a.y*a.y) end,
-- 4__index = {area = function(a) return a.x*a.x + a.y*a.y end,},
}
-- 5
point = ffi.metatype("point_t", mt)-- 6
local a = point(3, 4)
print(a.x, a.y)  --> 3  4
print(#a)        --> 5
print(a:area())  --> 25
local b = a + point(0.5, 8)
print(#b)        --> 12.5

Here’s the step-by-step explanation:

  1. This defines the C type for a two-dimensional point object.
  2. We have to declare the variable holding the point constructor first, because it’s used inside of a metamethod.
  3. Let’s define an __add metamethod which adds the coordinates of two points and creates a new point object. For simplicity, this function assumes that both arguments are points. But it could be any mix of objects, if at least one operand is of the required type (e.g. adding a point plus a number or vice versa). Our __len metamethod returns the distance of a point to the origin.
  4. If we run out of operators, we can define named methods, too. Here, the __index table defines an area function. For custom indexing needs, one might want to define __index and __newindex functions instead.
  5. This associates the metamethods with our C type. This only needs to be done once. For convenience, a constructor is returned by ffi.metatype(). We’re not required to use it, though. The original C type can still be used e.g. to create an array of points. The metamethods automatically apply to any and all uses of this type.

Please note, that the association with a metatable is permanent and the metatable must not be modified afterwards! Ditto for the __index table.

  1. Here are some simple usage examples for the point type and their expected results. The predefined operations (such as a.x) can be freely mixed with the newly defined metamethods. Note that area is a method and must be called with the Lua syntax for methods: a:area(), not a.area().

The C type metamethod mechanism is most useful when used in conjunction with C libraries that are written in an object-oriented style. Creators return a pointer to a new instance, and methods take an instance pointer as the first argument. Sometimes you can just point __index to the library namespace and __gc to the destructor and you’re done. But often enough you’ll want to add convenience wrappers, e.g. to return actual Lua strings or when returning multiple values.

舉個例子,如果你有一個 C 庫 mylib,它有一個方法 mylib:do_something(),你可以通過 __index 將該方法暴露給 Lua:

local ffi = require("ffi")
ffi.cdef[[typedef struct { int x, y; } MyObject;void do_something(MyObject* obj);
]]local mylib = ffi.load("mylib")-- 綁定一個 MyObject 類型
local MyObject = ffi.metatype("MyObject", {__index = mylib,  -- 將 __index 指向庫的命名空間
})-- 創建一個 MyObject 實例
local obj = MyObject()-- 直接使用 C 庫的方法
obj:do_something()

文檔中還提到,有時候你需要手動定義 __gc 來處理對象的銷毀。在 Lua 中創建的 C 對象,可能需要在 Lua 對象被垃圾回收時清理對應的 C 資源(例如關閉文件句柄、釋放內存等)。你可以通過 __gc 來定義析構函數。

local ffi = require("ffi")ffi.cdef[[typedef struct { int x, y; } MyObject;void destroy_object(MyObject* obj);
]]local mylib = ffi.load("mylib")local MyObject = ffi.metatype("MyObject", {-- 定義析構函數__gc = function(self)mylib.destroy_object(self)end
})-- 創建一個 MyObject 實例
local obj = MyObject()

Some C libraries only declare instance pointers as an opaque void * type. In this case you can use a fake type for all declarations, e.g. a pointer to a named (incomplete) struct will do: typedef struct foo_type *foo_handle. The C side doesn’t know what you declare with the LuaJIT FFI, but as long as the underlying types are compatible, everything still works.


例子:創建 userdata

如果不使用 LuaJIT,使用標準的 Lua 解釋器實現相同的程序,那么需要創建一個 C 模塊,模塊提供函數創建一個 userdata,并給它設置元表

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <math.h>
#include <stdlib.h>typedef struct {double x, y;
} point_t;// 創建 point 實例
static int point_new(lua_State *L) {double x = luaL_checknumber(L, 1);double y = luaL_checknumber(L, 2);point_t *p = (point_t *)lua_newuserdata(L, sizeof(point_t));p->x = x;p->y = y;luaL_getmetatable(L, "PointMT");lua_setmetatable(L, -2);return 1;
}// __add 運算符
static int point_add(lua_State *L) {point_t *a = (point_t *)luaL_checkudata(L, 1, "PointMT");point_t *b = (point_t *)luaL_checkudata(L, 2, "PointMT");lua_pushcfunction(L, point_new);lua_pushnumber(L, a->x + b->x);lua_pushnumber(L, a->y + b->y);lua_call(L, 2, 1);return 1;
}// __len 運算符
static int point_len(lua_State *L) {point_t *a = (point_t *)luaL_checkudata(L, 1, "PointMT");lua_pushnumber(L, sqrt(a->x * a->x + a->y * a->y));return 1;
}// area 方法
static int point_area(lua_State *L) {point_t *a = (point_t *)luaL_checkudata(L, 1, "PointMT");lua_pushnumber(L, a->x * a->x + a->y * a->y);return 1;
}// 獲取字段
static int point_get(lua_State *L) {point_t *p = (point_t *)luaL_checkudata(L, 1, "PointMT");const char *key = luaL_checkstring(L, 2);if (strcmp(key, "x") == 0) {lua_pushnumber(L, p->x);} else if (strcmp(key, "y") == 0) {lua_pushnumber(L, p->y);} else if (strcmp(key, "area") == 0) {lua_pushcfunction(L, point_area);} else {lua_pushnil(L);}return 1;
}// 注冊 point_t 相關的元表和方法
static void create_point_metatable(lua_State *L) {luaL_newmetatable(L, "PointMT");lua_pushstring(L, "__add");lua_pushcfunction(L, point_add);lua_settable(L, -3);lua_pushstring(L, "__len");lua_pushcfunction(L, point_len);lua_settable(L, -3);lua_pushstring(L, "__index");lua_pushcfunction(L, point_get);lua_settable(L, -3);lua_pop(L, 1);
}// 注冊模塊
static const luaL_Reg pointlib[] = {{"new", point_new},{NULL, NULL}
};int luaopen_point(lua_State *L) {create_point_metatable(L);luaL_register(L, "point", pointlib);return 1;
}

然后在 Lua 中使用這個模塊

local point = require("point")local a = point.new(3, 4)
print(a.x, a.y)   -- 輸出 3  4
print(#a)         -- 輸出 5
print(a:area())   -- 輸出 25local b = a + point.new(0.5, 8)
print(#b)         -- 輸出 12.5

整個流程要復雜很多。不得不說 LuaJIT 真牛逼!

Translating C Idioms

Here’s a list of common C idioms and their translation to the LuaJIT FFI:
在這里插入圖片描述

To Cache or Not to Cache

The JIT compiler has special logic to eliminate all of the lookup overhead for functions resolved from a C library namespace! Thus it’s not helpful and actually counter-productive to cache individual C functions like this:

local funca, funcb = ffi.C.funca, ffi.C.funcb -- Not helpful!
local function foo(x, n)for i=1,n do funcb(funca(x, i), 1) end
end

This turns them into indirect calls and generates bigger and slower machine code. Instead, you’ll want to cache the namespace itself and rely on the JIT compiler to eliminate the lookups:

local C = ffi.C          -- Instead use this!
local function foo(x, n)for i=1,n do C.funcb(C.funca(x, i), 1) end
end

This generates both shorter and faster code. So don’t cache C functions, but do cache namespaces! Most often the namespace is already in a local variable at an outer scope, e.g. from local lib = ffi.load(…). Note that copying it to a local variable in the function scope is unnecessary.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/72107.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/72107.shtml
英文地址,請注明出處:http://en.pswp.cn/web/72107.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于 FastText、dlib 和 CppJieba 的中文語義相似度計算實踐

在自然語言處理(NLP)領域,語義相似度計算是許多任務的核心,例如問答系統、文本檢索和推薦系統。然而,中文因缺乏顯式分詞和復雜的語義結構,實現高效的語義對比具有一定挑戰性。 本文將介紹如何結合 CppJieba(高效中文分詞工具)、FastText(詞向量模型)和 dlib(機器學…

HCIA-11.以太網鏈路聚合與交換機堆疊、集群

鏈路聚合背景 拓撲組網時為了高可用&#xff0c;需要網絡的冗余備份。但增加冗余容易后會出現環路&#xff0c;所以我們部署了STP協議來破除環路。 但是&#xff0c;根據實際業務的需要&#xff0c;為網絡不停的增加冗余是現實需要的一部分。 那么&#xff0c;為了讓網絡冗余…

Unity基于C#+UGUI解決方案,制作每日簽到系統(本地存儲簽到數據)

一、需求介紹:基于本地存儲系統制作一個每日簽到系統界面,相關簽到界面如下圖所示,點擊“簽到有禮”按鈕后就會跳轉到“每日登錄禮”這個界面,點擊“立即簽到”按鈕之后,按鈕就會置灰,而且按鈕的文字會變成“等待明日”。 二、制作界面顯示相關功能,需要在Unity中新建一…

AI本地部署

文檔加載&#xff08;Document Loading&#xff09;&#xff1a;從多種不同來源加載文檔。LangChain提供了100多種不同的文檔加載器&#xff0c;包括PDF在內的非結構化的數據、SQL在內的結構化的數據&#xff0c;以及Python、Java之類的代碼等? ?文本分割&#xff08;Splitti…

精準車型識別:視覺分析技術的力量

隨著智慧城市和智能交通系統的快速發展&#xff0c;車型識別檢測成為交通管理、安全監控和數據分析的關鍵技術之一。利用視覺分析的方式&#xff0c;我們可以高效、準確地檢測監控下的車輛類型、車牌信息及車流量&#xff0c;為城市交通管理提供有力支持。本文將從背景、技術實…

上下文微調(Contextual Fine-Tuning, CFT)提高大型語言模型(LLMs)在特定領域的學習和推理能力

大型語言模型(LLMs)在開放領域任務中表現出色,但在快速演變的專業領域(如醫學、金融)中面臨挑戰: 知識更新難題:傳統指令微調(Instruction Fine-Tuning, IFT)依賴顯式指令,難以適應動態知識。災難性遺忘:持續預訓練(Continued Pretraining, CPT)可能導致模型遺忘已…

在 LaTeX 中強制表格位于頁面頂部

在 LaTeX 中強制表格位于頁面頂部&#xff0c;可以通過以下 多種方法結合使用&#xff0c;按優先級推薦&#xff1a; 方法 1&#xff1a;使用 [!t] 位置限定符 原理&#xff1a;通過 [!t] 強制 LaTeX 優先將表格放置在頁面頂部&#xff08;Top&#xff09;&#xff0c;! 表示忽…

kotlin與MVVM的結合使用總結(二)

在 MVVM&#xff08;Model - View - ViewModel&#xff09;架構中&#xff0c;M 層即 Model 層&#xff0c;主要負責數據的管理、存儲和獲取&#xff0c;它與業務邏輯和數據處理相關。在 Kotlin 中實現 MVVM 的 M 層&#xff0c;通常會涉及數據類的定義、數據的本地存儲與遠程獲…

電子元器件選型與實戰應用—16 怎么選一個合適的MCU芯片?

文章目錄 1. 選型要素1.1 價格1.2 技術支持1.3 廠家優勢1.4 功耗1.5 特殊功能1.6 統計外設1.7 確定外設占用的內存和flash大小1.8 確定外設通信接口1.9 確定外設通信接口的電平1.10 確定外設的GPIO數量1.11 確定外設的供電和功耗1.12 確定外設GPIO的種類1.13 確定ADC的數量1.14…

VSCode 搭建C++編程環境 2025新版圖文安裝教程(100%搭建成功,VSCode安裝+C++環境搭建+運行測試+背景圖設置)

名人說&#xff1a;博觀而約取&#xff0c;厚積而薄發。——蘇軾《稼說送張琥》 創作者&#xff1a;Code_流蘇(CSDN)&#xff08;一個喜歡古詩詞和編程的Coder&#x1f60a;&#xff09; 目錄 一、VScode下載及安裝二、安裝 MinGW-w64 工具鏈三、Windows環境變量配置四、檢查 M…

Django系列教程(7)——路由配置URLConf

目錄 URLconf是如何工作的? path和re_path方法 更多URL配置示例 URL的命名及reverse()方法 使用命名URL 硬編碼URL - 不建議 URL指向基于類的視圖(View) 通過URL傳遞額外的參數 小結 Django的項目文件夾和每個應用(app)目錄下都有urls.py文件&#xff0c;它們構成了D…

transformer bert 多頭自注意力

輸入的&#xff08;a1,a2,a3,a4&#xff09;是最終嵌入&#xff0c;是一個(512,768)的矩陣&#xff1b;而a1是一個token&#xff0c;尺寸是768 a1通過wq權重矩陣&#xff0c;經過全連接變換得到查詢向量q1&#xff1b;a2通過Wk權重矩陣得到鍵向量k2&#xff1b;q和k點乘就是值…

Spring Boot + MyBatis-Plus 項目目錄結構

以下是一個標準的 Spring Boot MyBatis-Plus 項目目錄結構及文件命名規范&#xff0c;包含每個目錄和文件的作用說明&#xff0c;適用于中大型項目開發&#xff1a; 項目根目錄結構 src/ ├── main/ │ ├── java/ # Java 源代碼 │ │ └── com/…

Webpack優化前端性能

Webpack優化前端性能☆☆ 涵蓋了代碼分割、懶加載、壓縮、緩存優化、Tree Shaking、圖片優化、CDN使用等多個方面。 Webpack優化前端性能詳解(2025綜合實踐版) Webpack作為現代前端工程化的核心工具,其優化能力直接影響項目的首屏速度、交互流暢度和用戶體驗。以下從代碼維…

ardunio R4 WiFi連接實戰

ardunio WiFi連接模板 ardunio R4 WiFi 開發板有著不錯的性能和板載內存&#xff0c;本機自帶 WiFi 連接模塊&#xff0c;可以完成簡單的網絡服務。對于這個小東西我情有獨鐘&#xff0c;也總希望能夠用它來做些什么&#xff0c;所以先從 WiFi 連接開始學起&#xff0c;未來考…

C++11 編譯使用 aws-cpp-sdk

一、對sdk的編譯前準備 1、軟件需求 此文檔針對于在Linux系統上使用源碼進行編譯開發操作系統使用原生的contos7Linux。機器配置建議 內存8G以上,CPU 4個 以上GCC 4.9.0 及以上版本Cmake 3.12以上 3.21以下apt install libcurl-devel openssl-devel libuuid-devel pulseaudio-…

得物 Android Crash 治理實踐

一、前言 通過修復歷史遺留的Crash漏報問題&#xff08;包括端側SDK采集的兼容性優化及Crash平臺的數據消費機制完善&#xff09;&#xff0c;得物Android端的Crash監控體系得到顯著增強&#xff0c;使得歷史Crash數據的完整捕獲能力得到系統性改善&#xff0c;相應Crash指標也…

SpringBoot3+Lombok如何配置logback輸出日志到文件

Background/Requirement SpringBoot3Lombok如何配置logback輸出日志到文件&#xff0c;因為我需要對這些日志進行輸出&#xff0c;控制臺輸出和文件輸出&#xff0c;文件輸出是為了更好的作為AuditLog且支持滾動式備份&#xff0c;每天一個文件。 Technical Solution 1.確保你…

主流向量數據庫對比

在 AI 的 RAG&#xff08;檢索增強生成&#xff09;研發領域&#xff0c;向量數據庫是存儲和查詢向量嵌入的核心工具&#xff0c;用于支持高效的語義搜索和信息檢索。向量嵌入是文本或其他非結構化數據的數值表示&#xff0c;RAG 系統通過這些嵌入從知識庫中檢索相關信息&#…

搞定python之四----函數、lambda和模塊

本文是《搞定python》系列專欄的第四篇&#xff0c;通過代碼演示列python自定義函數、lambda和模塊的用法。本文學習完成后&#xff0c;python的基礎知識就完了。后面會學習面向對象的內容。 1、自定義函數 # 測試python自定義函數# 有參數&#xff0c;沒有返回值 def say_he…