Nanolog起步笔记-8-log解压过程(2)寻找meta

@TOC

写在前面

如前。建立工程进行跟踪后,发现自己对Nanolog的理解还是太少了。
其过程,还是相对比较复杂。
以及有一些信息,这前的分析中,没有注意到。
本节,不向前推进,进行一定的总结与学习的准备。

另外,这里先不得不说抱歉,因为工时很紧张,只能问了GPT一些内容,一会贴在下面。
国内我发现,只要是这类底层的工作,很少有人问津,所以,虽然内网有一些关于nanolog的内容,大多与我要做的事情无关。大多是介绍性的工作。客观说,不如前面我分析的内容更加细致。

但是,前面我没有详研究和解说,日志存储(序列化)的过程,只提到两个线程。
所以,这里也一并说一下。

fmtID

在开始之前,我解释一下fmtid与logid之间的关系,二者在后面的内容中频繁出现。
实际二者是同一个值,只是上下文不同,所以,是两个变量。
logid,同样的meta的信息,只是它出现在log过程;
fmtid,与logid是同一东西,只是更精准地指出,这是一个元信息id.
见如下代码:
在这里插入图片描述这句非常清楚地说明了二者是同一个实在。

GPT的部分解释

Meta Information in NanoLog

NanoLog separates the meta-data (such as timestamp, log level, and thread ID) from the actual log message content. This is done to reduce the overhead of logging operations while maintaining important context for each log entry.

In NanoLog, the meta information typically includes:

  • Log Level: The severity of the log entry (e.g., DEBUG, INFO, WARNING, ERROR).
  • Timestamp: The timestamp of when the log entry was created.
  • Thread ID: Identifies the thread that generated the log message.
  • Log ID: A unique identifier for each log entry to help with tracing.

Storage of Meta Information:

In NanoLog, the meta information is typically stored at the start of each log entry. The log file format is designed so that each entry has a fixed-size structure that begins with this meta information, followed by the actual log message.

For example:

  1. Fixed-Size Log Header:
    • A small header might contain the timestamp, log level, thread ID, and potentially other metadata.
  2. Log Message:
    • The actual content of the log message is stored after the meta-information in the log entry.
  3. Indexing:
    • The log file is structured so that these entries are efficiently indexed, allowing fast access to logs in real-time.

The key advantage of this approach is that by separating the metadata (which is small) from the actual log message (which can be large), NanoLog achieves high performance even under heavy logging loads.

Where is Meta-Data Saved in NanoLog Files?

The metadata in NanoLog is stored as part of each log entry. A typical log entry in NanoLog consists of:

  1. Meta-Data Block: A fixed-size block containing critical metadata (timestamp, thread ID, log level, etc.).
  2. Message Block: The actual log message (which can vary in size but is fixed within the log entry itself).

Since NanoLog uses a fixed entry format, the meta-data is usually the first few bytes of each log entry, followed by the log message itself. This allows for efficient parsing and processing of logs with minimal overhead.

To better understand the file format, you would need to dive into the NanoLog code itself, which is typically available in the GitHub repository, where the structure of the log entries (including where and how the meta-data is stored) is fully defined.

Example of Where Meta-Data is Located:

If you look at the source code (the header files that define the log entry structures), you’ll likely find something like this for the log entry format:

struct NanoLogEntry {
    uint64_t timestamp;  // 8 bytes
    uint32_t log_level;  // 4 bytes
    uint32_t thread_id;  // 4 bytes
    char message[MAX_LOG_MESSAGE_SIZE]; // Variable size message content
};

In this example:

  • timestamp and log_level are part of the meta-data.
  • The actual log message (message) follows this meta section in the log entry.

Summary

  • Meta Information in NanoLog typically includes the timestamp, log level, and thread ID.
  • This meta-information is stored at the start of each log entry, followed by the actual log message.
  • The log format is designed for high performance, minimizing the overhead of writing logs while still providing important contextual information for each log entry.

If you’re looking for more specific details on how the format is laid out or how meta-data is serialized, checking the official NanoLog GitHub repository would be helpful, as the source code would provide exact details about the file format and how metadata is handled internally.


You:

Please explain more information about what is entry? some definations of entry code in nanolog as follwing:

namespace Log {
    /**
     * Marks the beginning of a log entry within the StagingBuffer waiting
     * for compression. Every instance of this header in the StagingBuffer
     * corresponds to a user invocation of the log function in the NanoLog
     * system and thus every field is uncompressed to lower the compute time
     * for that invocation.
     */
    struct UncompressedEntry {
        // Uniquely identifies a log message by its format string and file
        // location, assigned at compile time by the preprocessor.
        uint32_t fmtId;

        // Number of bytes for this header and the various uncompressed
        // log arguments after it
        uint32_t entrySize;

        // Stores the rdtsc() value at the time of the log function invocation
        uint64_t timestamp;

        // After this header are the uncompressed arguments required by
        // the original format string
        char argData[0];
    };

    /**
     * 2-bit enum that differentiates entries in the compressed log. These
     * two bits **MUST** be at the beginning of each entry in the log
     * to facilitate decoding.
     */
    enum EntryType : uint8_t {
        // Marks an invalid entry in the compressed log. This value is
        // deliberately 0 since \0's are used to pad the output to 512B
        // in the final output.
        INVALID = 0,

        // Indicates the beginning of a CompressedRecordEntry when within a
        // BufferExtent, otherwise marks the beginning of a dictionary fragment
        LOG_MSGS_OR_DIC = 1,

        // Indicates a BufferExtent struct
        BUFFER_EXTENT = 2,

        // Indicates a CheckPoint struct
        CHECKPOINT = 3
    };

    /**
     * All data structures in the compressed log must contain the EntryType
     * in the first two bits and this structure is used to extract those bits
     * when the type/identify is unknown.
     */
    NANOLOG_PACK_PUSH
    struct UnknownHeader {
        uint8_t entryType:2;
        uint8_t other:6;
    };
    NANOLOG_PACK_POP

    static_assert(sizeof(UnknownHeader) == 1, "Unknown Header should have a"
            " byte size of 1 to ensure that we can always determine the entry"
            " that follows with 1 byte peeks.");

    /**
     * Marks the beginning of a compressed log message and after this structure
     * comes the compressed arguments. The exact layout of the compressed
     * arguments is generated at compile-time (see the Python preprocessor),
     * but what comes immediately after this header are:
     *      (1-4 bytes) pack()-ed FormatId
     *      (1-8 bytes) pack()-ed rdtsc() timestamp
     *      (0-n bytes) arguments (determined by preprocessor)
     */
    NANOLOG_PACK_PUSH
    struct CompressedEntry {
        // Byte representation of an EntryType::LOG_MSGS_OR_DIC to identify this
        // as a CompressedRecordEntry.
        uint8_t entryType:2;

        // Value returned by pack(formatId), subtracted by 1 to save space.
        // i.e. if pack() returned 2 this value is 1.
        // TODO(syang0) this is an abstraction failure; it's not treating
        // the value returned by pack() as a black box.
        uint8_t additionalFmtIdBytes:2;

        // Value returned by pack(timestamp)
        uint8_t additionalTimestampBytes:4;
    };
    NANOLOG_PACK_POP

ChatGPT:

The code you’ve shared from NanoLog defines several structures that represent different types of log entries in both uncompressed and compressed formats. Let’s break it down step by step to better understand the various “entries” in the context of NanoLog and the role of each field:

1. UncompressedEntry

This structure represents an uncompressed log entry in the StagingBuffer (a temporary storage area before compression). When a log function is invoked, an UncompressedEntry is created to hold the log message, along with all the relevant metadata.

Structure:
struct UncompressedEntry {
    uint32_t fmtId;         // Uniquely identifies the log message format, assigned at compile time.
    uint32_t entrySize;     // The size of the header and the uncompressed arguments that follow.
    uint64_t timestamp;     // The timestamp when the log function was called (using rdtsc(), a high-resolution timer).
    char argData[0];        // The uncompressed arguments required by the original format string (dynamic size).
};
Field Details:
  • fmtId: This is a unique identifier that is generated by the preprocessor based on the log’s format string and its location in the source code. It’s used to correlate a specific log message to its format during compression and decompression.
  • entrySize: This field holds the size of the entire log entry (header + arguments), which helps in efficiently processing the log message in memory.
  • timestamp: This stores the timestamp (using rdtsc(), a CPU instruction) at the time of the log message invocation. This provides a high-resolution timestamp for when the log entry was created.
  • argData: The arguments for the log message, stored as raw bytes after the header. These are the actual parameters that were passed to the logging function, formatted according to the format string. The size of this field is variable, and argData[0] indicates a flexible array (the actual size depends on the number of arguments in the log statement).

2. EntryType Enum

This is an enumeration used to differentiate between different types of log entries in the compressed log. It helps the decoder determine what type of log entry it is dealing with.

Enum:
enum EntryType : uint8_t {
    INVALID = 0,              // Marks an invalid entry (used for padding in compressed output).
    LOG_MSGS_OR_DIC = 1,      // Marks the beginning of a CompressedRecordEntry or a dictionary fragment.
    BUFFER_EXTENT = 2,        // Marks a BufferExtent struct (part of the log compression).
    CHECKPOINT = 3            // Marks a CheckPoint struct (likely a synchronization point in logging).
};

Each log entry has a 2-bit identifier (entryType) that helps in determining how the entry should be processed or decoded. These types are used in compressed logs to distinguish between different segments, like regular log messages, dictionary fragments (used for compression), and other data structures related to the log format.

3. UnknownHeader

This structure is used to help decode log entries when the type of the entry is not known. It allows the decoder to check the first byte of a log entry to determine its type.

Structure:
struct UnknownHeader {
    uint8_t entryType:2;  // The 2-bit entry type identifier.
    uint8_t other:6;       // The remaining 6 bits are reserved for future use.
};

Since the entryType is 2 bits, this structure ensures that we can always extract the entry type efficiently and allows the decoder to figure out what type of log entry follows.

4. CompressedEntry

This structure represents a compressed log entry. After an UncompressedEntry is created in the staging buffer, it will be eventually compressed. The CompressedEntry holds the compressed version of the log entry, where certain fields are encoded in a way that reduces space.

Structure:
struct CompressedEntry {
    uint8_t entryType:2;                // 2-bit entry type identifier (Log message or dictionary fragment).
    uint8_t additionalFmtIdBytes:2;     // The number of bytes used to store the format ID (compressed).
    uint8_t additionalTimestampBytes:4; // The number of bytes used to store the timestamp (compressed).
};
Field Details:
  • entryType: This 2-bit field indicates the type of the entry (like a log message or a dictionary fragment).
  • additionalFmtIdBytes: This field represents the number of additional bytes used to store the fmtId in the compressed entry. The fmtId is a key part of identifying the log format, and NanoLog uses a compression technique to reduce the size of this identifier.
  • additionalTimestampBytes: This field represents the number of bytes used to store the timestamp in the compressed entry. Like fmtId, the timestamp is also compressed to save space, and this field indicates how many bytes are used in the compressed entry.

Summary of Entry Code and Structure:

  1. UncompressedEntry: Represents an entry in the staging buffer before compression. It includes metadata (format ID, timestamp, etc.) and the raw log arguments.
  2. EntryType Enum: Specifies the type of log entry in the compressed log, helping the decoder understand how to interpret the data.
  3. UnknownHeader: A helper structure used to decode unknown log entries and extract the entry type.
  4. CompressedEntry: Represents a compressed log entry, where certain fields like fmtId and the timestamp are compressed to save space.

Flow of NanoLog Entries:

  1. Log Function Call: When a log function is invoked, an UncompressedEntry is created in the StagingBuffer.
  2. Compression: The uncompressed log entry is then compressed into a CompressedEntry to reduce space. The compression involves reducing the size of fields like fmtId and the timestamp.
  3. Storage: These compressed entries are then stored in the log file or memory buffer. Each entry is prefixed by an EntryType to specify what kind of data follows (log message, dictionary, buffer extent, etc.).
  4. Decoding: When logs are read, the system uses the EntryType to decode each entry appropriately.

NanoLog’s efficient handling of log entries with compression and flexible metadata structures helps in logging at scale without introducing significant performance overhead.


You:

OK, Maybe my problem is that I don’t know enough about the log record writing process, so it’s difficult for me to understand the decompression process. Can you explain the “Flow of NanoLog Entries” in more detail? Escepially the rcord write process.


ChatGPT:

Understanding the record writing process is crucial to understanding how logs are compressed and later decompressed in NanoLog. Here’s a more detailed breakdown of the log writing process in NanoLog, focusing on how records are created, written, and eventually compressed.

1. Log Record Writing Process Overview

The process of writing a log entry in NanoLog can be thought of as a series of steps where a log message is created, stored temporarily, and then eventually compressed before being written to the final storage medium (such as a file or memory buffer). Here’s an overview of the flow:

  1. Log Function Invocation: A log function is called by the application.
  2. Create an Uncompressed Log Entry: An uncompressed log entry is created in a StagingBuffer.
  3. Compress the Log Entry: The uncompressed entry is then compressed (if compression is enabled).
  4. Store the Compressed Entry: The compressed log entry is stored in a final destination (a memory buffer or file).

2. Step-by-Step Breakdown of Log Record Writing

Let’s walk through these steps in more detail:

Step 1: Log Function Invocation

When a log function is called (for example, NanoLog::log()), the system prepares to write a log entry. In NanoLog, log entries are created based on format strings and arguments passed to the log function.

For example, a log function might be called like this:

log("File opened: {} at time {}", filename, timestamp);

Here, "File opened: {} at time {}" is the format string, and filename and timestamp are the arguments.

Step 2: Create an Uncompressed Log Entry (Staging Buffer)

When the log function is invoked, an UncompressedEntry is created in the StagingBuffer.

UncompressedEntry Structure:
struct UncompressedEntry {
    uint32_t fmtId;         // Unique identifier for the log message's format
    uint32_t entrySize;     // Total size of the entry (header + arguments)
    uint64_t timestamp;     // Timestamp (e.g., using rdtsc()) when the log is written
    char argData[0];        // Arguments for the log message (variable size)
};
  • fmtId: This is a unique identifier for the format string used in the log message. It’s computed at compile time (using macros or preprocessor functions) and helps identify the exact format for the log entry.

  • entrySize: This indicates the total size of the log entry (header + arguments). This size is used for efficient memory management.

  • timestamp: The timestamp (typically generated using rdtsc() or a similar high-resolution timer) represents the exact time the log entry was created.

  • argData: This is a flexible array that contains the actual arguments that were passed to the log function. This could be a string, integer, or other data types, depending on the format string and arguments.

The UncompressedEntry is stored temporarily in memory, and the StagingBuffer is used to manage all log entries before they are compressed.

Step 3: Compress the Log Entry

Once an UncompressedEntry is created, the system compresses it to reduce the amount of storage required. The compression process can vary depending on the log entry type and the specific compression algorithm used.

CompressedEntry Structure:
struct CompressedEntry {
    uint8_t entryType:2;                // Type of the entry (e.g., regular log message or dictionary fragment)
    uint8_t additionalFmtIdBytes:2;     // Number of bytes for the compressed format ID
    uint8_t additionalTimestampBytes:4; // Number of bytes for the compressed timestamp
};
  • Compression: The fmtId and timestamp are compressed to reduce the size of the log entry. This is done by packing the values into smaller byte representations. For instance:

    • The fmtId might be packed to use fewer bytes based on the actual size of the format string.
    • Similarly, the timestamp might be stored in fewer bytes, depending on its precision.
  • entryType: The entryType field is a 2-bit value indicating the type of the entry. This helps the decoder distinguish between different kinds of entries (e.g., regular log messages, dictionary entries used for compression, etc.).

Step 4: Store the Compressed Entry

Once the log entry is compressed, the compressed entry is stored in the final log buffer or written directly to disk, depending on the configuration.

  • The compressed entry will include both the compressed metadata (e.g., fmtId, timestamp) and the compressed arguments (the original arguments passed to the log function). These are stored together as a single entry.

  • Buffering: NanoLog uses buffers (like StagingBuffer and final storage buffers) to hold log entries temporarily before they are flushed to disk. If logging is happening frequently, the logs will be written in batches to minimize I/O operations.

  • Final Log Format: The final compressed log entries are written as a sequence of CompressedEntry structures, where each entry starts with an EntryType to help decode the rest of the entry correctly.

3. Summary of the Flow

  • Log Function Call: A log function is called, passing a format string and arguments.
  • Uncompressed Entry Creation: An uncompressed log entry is created in the StagingBuffer. This includes the format ID, timestamp, and arguments.
  • Compression: The log entry is compressed, reducing the size of the format ID, timestamp, and arguments.
  • Storing: The compressed log entry is written to memory or a log file, with a header (indicating entry type) and the compressed data.

4. How Compression Works (In More Detail)

NanoLog uses a custom compression scheme to reduce the size of log entries. This typically involves:

  1. Packing the Format ID (fmtId): Instead of storing a full 32-bit or 64-bit identifier, NanoLog uses a variable-length encoding scheme to save space. The packed fmtId takes fewer bytes if the format ID is small.

  2. Packing the Timestamp: Similar to the format ID, the timestamp is packed into a smaller representation. For example, if the timestamp has a high precision (e.g., nanoseconds), it might be compressed to a smaller number of bytes, reducing memory usage.

  3. Argument Compression: The arguments passed to the log function are also compressed using a method that can vary depending on the type of data. For instance, strings may be stored as compressed byte arrays.

  4. Dictionary Compression: NanoLog may also use dictionary compression for repetitive log entries. For example, if the same format string appears multiple times, it can be compressed into a dictionary entry and stored only once, saving space.

5. Why Compress Log Entries?

  • Reduced Storage: By compressing log entries, NanoLog can store much more log data in a given amount of space, which is critical in high-performance systems that generate large volumes of logs.
  • Faster Log Writing: Compression reduces the amount of data written to disk, speeding up the process of storing logs.
  • Efficient Memory Usage: By packing the data efficiently (i.e., using variable-length encoding), NanoLog reduces memory usage, which is important in memory-constrained environments.

Final Thoughts

The NanoLog writing process is designed to ensure minimal performance overhead while enabling high-throughput logging. By using compression, efficient memory buffers, and structured entries, NanoLog allows logging at scale without significantly impacting system performance.

In essence:

  1. Log entries are created uncompressed (in memory).
  2. They are then compressed to reduce their size and improve efficiency.
  3. Finally, compressed entries are written to disk or memory buffers for later use.

This process ensures that the logs can be stored efficiently and later decoded with minimal overhead.

后记

上面的解释,相对详细解释了一些疑问。
例如,

meta在entry 的头部

In NanoLog, the meta information is typically stored at the start of each log entry.

什么是entry

目前还不能肯定,但可以看到,一个线程,在客户端是一个entry。应该是这样的设计。
在客户端的视角,一个entry包含一个。UncompressedEntry
这样,每个entry 包含了meta+data
UncompressedEntry是客户端的数据。
尽管,我们现在不清楚所谓的压缩的细节,但现在清楚,在客户端,采用原始结构块,由服务端将之压缩后,存入了文件。
所以,显然文件中用的是CompressedEntry。
而且CompressedEntry,也是1字节对齐的,从这个特性来看,也确是面向文件的定义。

这一段似乎是要点

enum EntryType : uint8_t {
    INVALID = 0,              // Marks an invalid entry (used for padding in compressed output).
    LOG_MSGS_OR_DIC = 1,      // Marks the beginning of a CompressedRecordEntry or a dictionary fragment.
    BUFFER_EXTENT = 2,        // Marks a BufferExtent struct (part of the log compression).
    CHECKPOINT = 3            // Marks a CheckPoint struct (likely a synchronization point in logging).
};

似乎LOG_MSGS_OR_DIC 是我们要找的meta 的定义。

这两段很有价值

3. Summary of the Flow

  • Log Function Call: A log function is called, passing a format string and arguments.
  • Uncompressed Entry Creation: An uncompressed log entry is created in the StagingBuffer. This includes the format ID, timestamp, and arguments.
  • Compression: The log entry is compressed, reducing the size of the format ID, timestamp, and arguments.
  • Storing: The compressed log entry is written to memory or a log file, with a header (indicating entry type) and the compressed data.

4. How Compression Works (In More Detail)

NanoLog uses a custom compression scheme to reduce the size of log entries. This typically involves:

  1. Packing the Format ID (fmtId): Instead of storing a full 32-bit or 64-bit identifier, NanoLog uses a variable-length encoding scheme to save space. The packed fmtId takes fewer bytes if the format ID is small.

  2. Packing the Timestamp: Similar to the format ID, the timestamp is packed into a smaller representation. For example, if the timestamp has a high precision (e.g., nanoseconds), it might be compressed to a smaller number of bytes, reducing memory usage.

  3. Argument Compression: The arguments passed to the log function are also compressed using a method that can vary depending on the type of data. For instance, strings may be stored as compressed byte arrays.

  4. Dictionary Compression: NanoLog may also use dictionary compression for repetitive log entries. For example, if the same format string appears multiple times, it can be compressed into a dictionary entry and stored only once, saving space.

小结

事情好像是越来越朝着不可控的方向发展了。
好消息是,所谓的压缩,只是对log record,并不是对meta data;只是战术层面的压缩。
坏消息是,这种压缩似乎对我正在要做的事,没有什么好处。
当然,另一个问题,还是没有得到meta在哪里的问题。正在犹豫,是不是去server端看一下压缩的过程。
后续的要点,还是分析解压的过程。如果分析的结果并不好,例如,原始信息都被打散了,那么就需要重头再来,要么从未解压的格式着手,要么,自己写自己的序列化的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/934364.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java-22 深入浅出 MyBatis - 手写ORM框架3 手写SqlSession、Executor 工作原理

点一下关注吧!!!非常感谢!!持续更新!!! 大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了: MyBatis&#xff…

Git:常用命令

一、查看当前分支 git branch 二、查看所有分支 git branch -a 三、切换到远程分支 git checkout origin/分支名 示例:git checkout origin/dev 四、拉取远程分支代码 git pull origin 分支名 示例:git pull origin dev 五、常用指令 查看暂存区…

算法1(蓝桥杯18)-删除链表的倒数第 N 个节点

问题: 给你一个链表,删除链表的倒数第 n 个节点,并且返回链表的头节点。 输入:head 1 -> 2 -> 3 -> 4 -> 5 -> null, n 2 输出:1 -> 2 -> 3 -> 5 -> null输入:head 1 ->…

H5接入Steam 获取用户数据案例 使用 OpenID 登录绑定公司APP账户 steam公开用户信息获取 steam webapi文档使用

官方文档地址 1.注册 Steam API Key: 你需要一个 Steam Web API Key,可以在 Steam API Key 页面 获取。https://steamcommunity.com/dev/apikey 这里开发做demo用自己steam账户的就好,后续上线要用公司的账户 2.使用 OpenID 登录&#xff…

TCP的“可靠性”(上)

目录 TCP的“可靠性”(上)确认应答(可靠性传输的基础)超时重传连接管理(三次握手,四次挥手) TCP的“可靠性”(上) 想必大家都或多或少的听说过TCP的特性:有连…

九、页面级变量的状态管理

状态管理概述 在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。 自定义组件拥有变量,变…

vs打开unity项目 新建文件后无法自动补全

问题 第一次双击c#文件自动打开vs编辑器的时候能自动补全,再一次在unity中新建c#文件后双击打开发现vs不能自动补全了。每次都要重新打开vs编辑器才能自动补全,导致效率很低,后面发现是没有安装扩展,注意扩展和工具的区别。 解决…

责任链模式的理解和实践

责任链模式(Chain of Responsibility)是行为型设计模式之一,它通过将多个对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。这个模式的主要目的是将请求的发送者和接收者解耦,使请求沿着处理链传…

软件工程——期末复习(3)

一、题目类(老师重点提到过的题目) 1、高可靠性是否意味着高可用性?试举例证明自己的观点? 答:高可靠性不意味着高可用性 可靠性说明系统已经准备好,马上可以使用;可用性是系统可以无故障的持续运行,是一…

SList(单链表)

文章目录 一:线性表二:数组2.1数组在内存中的存储 三:链式结构四:单链表4.1概念与结构4.1.1概念4.1.2 结构(节点)4.1.3链表的性质4.1.4链表的打印 4.2实现单链表 结语 欢迎大家来到我的博客,给生…

VTK知识学习(21)- 数据的读写

1、前言 对于应用程序而言,都需要处理特定的数据,VTK应用程序也不例外。 VTK应用程序所需的数据可以通过两种途径获取: 第一种是生成模型,然后处理这些模型数据(如由类 vtkCylinderSource 生成的多边形数据); 第二种是从外部存储介质里导…

Nignx部署Java服务测试使用的Spring Boot项目Demo

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

虚幻引擎生存建造系统

先做一个建造预览模式,按下按键B后进入建造预览模式 首先创建自定义事件Preview Loop 用射线追踪摆放物体预览位置,并做一个预览材质 增强输入设置按键 每帧判断是否进入建造模式 预览模式制作成功! 接着做点击左键放置物品&#xff0…

Blender中使用BlenderGIS插件快速生成城市建筑模型

导入下载 BlenderGIS 插件 去github上下载其压缩包,地址如下: https://github.com/domlysz/BlenderGIS 在BlenderGIS中导入这个插件压缩包: 点击上方菜单栏的编辑,点击偏好设置 在插件>从磁盘安装中导入刚刚下载的压缩包 可…

C语言期末复习

1、任意输入一个半径给r&#xff0c;求圆的面积。 #include <stdio.h> #include <windows.h> void main() { double r,s; printf("输入一个半径给r"); scanf("%lf",&r); sr*r*3.1415926; printf("%lf",s); system(&qu…

深圳大学《2024年904自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《深圳大学904自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

transformers生成式对话机器人

简介 生成式对话机器人是一种先进的人工智能系统&#xff0c;它能够通过学习大量的自然语言数据来模拟人类进行开放、连贯且创造性的对话。与基于规则或检索式的聊天机器人不同&#xff0c;生成式对话机器人并不局限于预定义的回答集&#xff0c;而是可以根据对话上下文动态地…

NanoLog起步笔记-4-Server端的两个线程

nonolog起步笔记-4-Server端的两个线程 Server端的两个线程两个线程的角色与各自的职责RuntimeLogger::compressionThreadMain线程 详细学习一下相关的代码第三个线程第一次出现原位置swip buffer Server端的两个线程 如前所述&#xff0c;nanolog的server端&#xff0c;相对而…

Freertos任务切换

一、操作系统进行任务切换的时机&#xff1a; 采用信号量实现任务的互斥&#xff1a; 二、FreeRTOS 任务切换场合 PendSV 中断的时候提到了上下文(任务)切换被触发的场合&#xff1a; ● 可以执行一个系统调用 ● 系统滴答定时器(SysTick)中断。 1、执行系统调用 执行系统…

【硬件测试】基于FPGA的4FSK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.Verilog核心程序 4.开发板使用说明和如何移植不同的开发板 5.完整算法代码文件获得 1.算法仿真效果 本文是之前写的文章: 《基于FPGA的4FSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR》 的…