移動語義對性能優化的具體示例

前言

? ? ? ? 本文章對比了:小中大字符串在普通傳值、傳值移動、傳左值引用、傳右值引用、模板完美轉發、內聯版本等多種測試,對比各個方式的性能優異:

測試代碼1

#include <iostream>
#include <string>
#include <chrono>
#include <utility>
#include <vector>
#include <functional>// 測量函數
template<typename Func>
double measureTime(Func&& func, int iterations) {auto start = std::chrono::high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {func();}auto end = std::chrono::high_resolution_clock::now();return std::chrono::duration<double, std::milli>(end - start).count();
}class Media {
public:std::string mPath;// 0. 傳值void setPath0(std::string path) {mPath = path;}// 1. 傳值移動void setPath1(std::string path) {mPath = std::move(path);}// 2. 傳左值引用void setPath2(const std::string& path) {mPath = path;}// 3. 傳右值引用void setPath3(std::string&& path) {mPath = std::move(path);}// 4. 模板完美轉發template<typename T>void setPath4(T&& path) {mPath = std::forward<T>(path);}// 5. 內聯版本inline void setPathInline(const std::string& path) {mPath = path;}
};// 結果存儲結構
struct TestResult {std::string name;double time;
};int main() {const int N = 10000;const int WARMUP = 1000; // 預熱迭代// 測試不同大小的字符串std::vector<std::pair<std::string, std::string>> testStrings = {{"小字符串(24字節)", std::string(24, 'x')},{"中等字符串(1KB)", std::string(1024, 'x')},{"大字符串(1MB)", std::string(1024 * 1024, 'x')}};for (const auto& [sizeDesc, bigString] : testStrings) {std::cout << "\n===== 測試 " << sizeDesc << " =====\n";std::vector<TestResult> results;Media media;// 預熱for (int i = 0; i < WARMUP; ++i) {media.mPath = bigString;}// 0. 值傳遞版本測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath0(testStr);}, N);results.push_back({"setPath0(值傳遞,左值傳參)", time});}// 1. 傳值版本測試 (左值){std::string testStr = bigString;double time = measureTime([&]() {media.setPath1(testStr);}, N);results.push_back({"setPath1(傳值移動,左值傳參)", time});}// 傳值 + 右值傳參測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath1(std::move(testStr));testStr = bigString;  // 還原數據}, N);results.push_back({"setPath1(傳右值,右值傳參)", time});}// 2. 傳左值引用測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath2(testStr);}, N);results.push_back({"setPath2(傳左值引用)", time});}// 3. 傳右值引用測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath3(std::move(testStr));testStr = bigString;  // 還原數據}, N);results.push_back({"setPath3(傳右值引用)", time});}// 4. 模板完美轉發測試(左值){std::string testStr = bigString;double time = measureTime([&]() {media.setPath4(testStr);}, N);results.push_back({"setPath4(模板完美轉發,左值傳參)", time});}// 模板完美轉發測試(右值){std::string testStr = bigString;double time = measureTime([&]() {media.setPath4(std::move(testStr));testStr = bigString;  // 還原數據}, N);results.push_back({"setPath4(模板完美轉發,右值傳參)", time});}// 5. 內聯版本測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPathInline(testStr);}, N);results.push_back({"setPathInline(內聯版本)", time});}// 6. 直接賦值測試{std::string testStr = bigString;double time = measureTime([&]() {media.mPath = testStr;}, N);results.push_back({"直接賦值", time});}// 7. 直接賦值 + 移動{std::string testStr = bigString;double time = measureTime([&]() {media.mPath = std::move(testStr);testStr = bigString;  // 還原數據}, N);results.push_back({"直接賦值 + 移動", time});}// 輸出結果并排序std::sort(results.begin(), results.end(), [](const TestResult& a, const TestResult& b) {return a.time < b.time;});std::cout << "性能排名 (從快到慢):\n";for (size_t i = 0; i < results.size(); ++i) {std::cout << i+1 << ". " << results[i].name << " 耗時: " << results[i].time << " 毫秒";if (i == 0) {std::cout << " (基準)";} else {double slowdown = (results[i].time / results[0].time - 1.0) * 100.0;std::cout << " (慢 " << slowdown << "%)";}std::cout << "\n";}}return 0;
}

測試結果:

測試代碼2:

#include <iostream>
#include <string>
#include <chrono>
#include <utility>
#include <vector>
#include <functional>
#include <iomanip>
#include <random>
#include <numeric>// 測量函數
template<typename Func>
double measureTime(Func&& func, int iterations) {auto start = std::chrono::high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {func();}auto end = std::chrono::high_resolution_clock::now();return std::chrono::duration<double, std::milli>(end - start).count();
}//#define RESTORE_DATA  //還原數據class Media {
public:std::string mPath;void setPath0(std::string path) {mPath = path;}void setPath1(std::string path) {mPath = std::move(path);}void setPath2(const std::string& path) {mPath = path;}void setPath3(std::string&& path) {mPath = std::move(path);}template<typename T>void setPath4(T&& path) {mPath = std::forward<T>(path);}inline void setPathInline(const std::string& path) {mPath = path;}
};struct TestResult {std::string name;double time;
};// 格式化輸出浮點數
std::string formatDouble(double value, int precision = 2) {std::ostringstream oss;oss << std::fixed << std::setprecision(precision) << value;return oss.str();
}int main() {const int N = 10000;const int WARMUP = 1000;std::vector<std::pair<std::string, std::string>> testStrings = {{"小字符串(24字節)", std::string(24, 'x')},{"中等字符串(1KB)", std::string(1024, 'x')},{"大字符串(1MB)", std::string(1024 * 1024, 'x')}};for (const auto& [sizeDesc, bigString] : testStrings) {std::cout << "\n===== 測試 " << sizeDesc << " =====\n";std::vector<TestResult> results;Media media;// 預熱for (int i = 0; i < WARMUP; ++i) {media.mPath = bigString;}// 0. 值傳遞版本測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath0(testStr);}, N);results.push_back({"setPath0(值傳遞,左值傳參)", time});}// 0. 值傳遞,移動語義測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath0(std::move(testStr));#ifdef RESTORE_DATAtestStr = bigString;#endif}, N);results.push_back({"setPath0(值傳遞,移動語義)", time});}// 1. 傳值版本測試 (左值){std::string testStr = bigString;double time = measureTime([&]() {media.setPath1(testStr);}, N);results.push_back({"setPath1(傳值移動,左值傳參)", time});}// 傳值 + 右值傳參測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath1(std::move(testStr));#ifdef RESTORE_DATAtestStr = bigString;#endif}, N);results.push_back({"setPath1(傳右值,右值傳參)", time});}// 2. 傳左值引用測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath2(testStr);}, N);results.push_back({"setPath2(傳左值引用)", time});}// 2. 傳左值引用測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath2(std::move(testStr));}, N);results.push_back({"setPath2(傳右值引用)", time});}// 3. 傳右值引用測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPath3(std::move(testStr));#ifdef RESTORE_DATAtestStr = bigString;#endif}, N);results.push_back({"setPath3(傳右值引用)", time});}// 4. 模板完美轉發測試(左值){std::string testStr = bigString;double time = measureTime([&]() {media.setPath4(testStr);}, N);results.push_back({"setPath4(模板完美轉發,左值傳參)", time});}// 模板完美轉發測試(右值){std::string testStr = bigString;double time = measureTime([&]() {media.setPath4(std::move(testStr));#ifdef RESTORE_DATAtestStr = bigString;#endif}, N);results.push_back({"setPath4(模板完美轉發,右值傳參)", time});}// 5. 內聯版本測試{std::string testStr = bigString;double time = measureTime([&]() {media.setPathInline(testStr);}, N);results.push_back({"setPathInline(內聯版本)", time});}// 6. 直接賦值測試{std::string testStr = bigString;double time = measureTime([&]() {media.mPath = testStr;}, N);results.push_back({"直接賦值", time});}// 7. 直接賦值 + 移動{std::string testStr = bigString;double time = measureTime([&]() {media.mPath = std::move(testStr);#ifdef RESTORE_DATAtestStr = bigString;#endif}, N);results.push_back({"直接賦值 + 移動", time});}// 輸出結果std::cout << "\n性能對比:\n";std::cout << std::setw(45) << std::left << "方法"<< std::setw(15) << "耗時(ms)"<< "性能比較\n";std::cout << std::string(80, '-') << "\n";double baselineTime = results[0].time; // 使用第一個測試作為基準for (const auto& result : results) {double percentage = ((baselineTime) / result.time) * 100.0;std::cout << std::setw(45) << std::left << result.name<< std::setw(15) << formatDouble(result.time);if (std::abs(percentage) < 0.1) {std::cout << "基準性能";} else if (percentage > 0) {std::cout << "快 " << formatDouble(percentage) << "%";} else {std::cout << "慢 " << formatDouble(-percentage) << "%";}std::cout << "\n";}}return 0;
}

測試結果:

需要還原數據:

不需要還原數據: 注釋#define?RESTORE_DATA??

結論

1. 首先注意第一點,函數內適當使用移動語義確實可以提高效率,需要還原數據時大概在30%左右,不需要還原數據時,效率提高巨大。

2.?需要注意的是,如果傳遞給函數的值也是move過的話,反而因為move會把原來的變量給清除,所以如果后面還需要的話,需要還原,其實效率提高并沒有多少。

3. 接上一條,但是如果數據不需要了,不需要還原數據,那么效率提高將會極大,

? ? 具體代碼可以將代碼開頭的 #define?RESTORE_DATA給注釋掉。

4. 如果需要還原數據,并且兼顧代碼更好寫,那么左值引用是個不錯的選擇,還不用寫還原數據:

? ?

? ? ?可以說,即高效又方便。比起內聯函數和完美轉發,效率不遑多讓。

5. 如果不需要還原數據,那么下圖框出來的幾個都可以,setPath3右值引用兼顧代碼好寫和性能

6. 由數據可知,完美轉發雖然寫法復雜,但是兼容性好,性能高,如果掌握了其實更好用

完美轉發的優劣勢:

  • 完美轉發能夠保留參數的左值/右值屬性,使函數能夠根據參數的原始類型進行正確的轉發,避免了不必要的拷貝和轉換,提高了性能
  • 無需為左值和右值分別編寫重載函數,一個模板函數就能處理多種參數類型,減少代碼冗余
  • 可以編寫通用的工廠函數,將參數完美轉發給構造函數,便于實現代理、裝飾器等設計模式
  • 完美轉發會保留參數的const屬性,確保類型安全
  • 庫設計者可以提供更靈活的接口,而不必擔心參數傳遞的效率問題

最適合使用完美轉發的場景

  1. 通用工廠函數

  2. 轉發包裝器/代理函數
    • 當你需要包裝一個函數,同時保留其參數的所有特性時
  3. 可變參數模板函數

    • 處理不定數量、不定類型的參數時
  4. 構建通用容器

    • 實現如emplace_back等需要直接構造對象的容器方法
  5. 中間層API設計

    • 當你的函數只是將參數傳遞給另一個函數,而不做任何處理時
  6. 性能關鍵的代碼

    • 需要避免不必要拷貝的性能敏感代碼

不適合使用完美轉發的場景

  1. 簡單的函數接口

    • 如果參數類型固定且簡單,使用常規引用可能更清晰
  2. 需要明確參數類型的API

    • 當你希望API使用者明確知道參數如何傳遞時
  3. 教學或入門級代碼

    • 對于學習者來說,完美轉發可能過于復雜
  4. 需要對參數進行多次使用的場景

    • 由于右值引用可能被移動,如果需要多次使用參數,完美轉發可能不是最佳選擇

完美轉發其他實例代碼

#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <type_traits>// 用于顯示參數類型的輔助函數
template<typename T>
void showValueCategory(const std::string& funcName, T&& param) {std::cout << funcName << ": ";// 使用完整的類型判斷if (std::is_same<T, std::remove_reference_t<T>>::value) {std::cout << "參數是右值" << std::endl;}else if (std::is_lvalue_reference<T>::value) {std::cout << "參數是左值" << std::endl;}else {std::cout << "參數是轉發的右值" << std::endl;}
}//===== 示例1:字符串包裝類 =====
class Message {
public:// 構造函數使用完美轉發template<typename T>Message(T&& msg) : content_(std::forward<T>(msg)) {showValueCategory("Message構造", std::forward<T>(msg));// 顯示更詳細的類型信息std::cout << "  類型信息: " << (std::is_lvalue_reference<T>::value ? "左值引用" : std::is_rvalue_reference<T>::value ? "右值引用" : "值類型")<< std::endl;}const std::string& getContent() const { return content_; }private:std::string content_;
};//===== 示例2:工廠函數 =====
template<typename T, typename... Args>
std::unique_ptr<T> createObject(Args&&... args) {std::cout << "創建新對象...\n";// 顯示每個參數的類型信息(showValueCategory("工廠函數參數", std::forward<Args>(args)), ...);return std::make_unique<T>(std::forward<Args>(args)...);
}//===== 示例3:通用打印函數 =====
class Printer {
public:template<typename T>void print(T&& value) {showValueCategory("打印函數", std::forward<T>(value));std::cout << "打印內容: " << value << std::endl;}
};//===== 示例4:參數轉發容器 =====
template<typename T>
class Container {
public:template<typename Arg>void add(Arg&& arg) {showValueCategory("Container添加", std::forward<Arg>(arg));data_.emplace_back(std::forward<Arg>(arg));}void showAll() const {std::cout << "容器內容:";for (const auto& item : data_) {std::cout << item.getContent() << " ";}std::cout << std::endl;}private:std::vector<T> data_;
};// 輔助函數:顯示對象移動狀態
void showMove(const std::string& str) {std::cout << "String '" << str << "' 被移動" << std::endl;
}int main() {std::cout << "\n===== 完美轉發示例 =====\n";// 測試1:基礎構造函數轉發std::cout << "\n1. 測試基礎構造函數轉發:" << std::endl;{std::string str = "Hello";                  // 創建左值std::cout << "傳遞左值:" << std::endl;Message msg1(str);                          // 傳遞左值std::cout << "傳遞右值字面量:" << std::endl;Message msg2("World");                      // 傳遞右值字面量std::cout << "傳遞移動的值:" << std::endl;Message msg3(std::move(str));              // 傳遞右值(移動)}// 測試2:工廠函數轉發std::cout << "\n2. 測試工廠函數轉發:" << std::endl;{std::string name = "Factory Object";std::cout << "\n使用左值創建:" << std::endl;auto msg1 = createObject<Message>(name);std::cout << "\n使用右值創建:" << std::endl;auto msg2 = createObject<Message>(std::string("Direct String"));std::cout << "\n使用字符串字面量創建:" << std::endl;auto msg3 = createObject<Message>("Literal String");}// 測試3:打印函數轉發std::cout << "\n3. 測試打印函數轉發:" << std::endl;{Printer printer;std::string text = "Left Value Text";std::cout << "打印左值:" << std::endl;printer.print(text);std::cout << "打印右值:" << std::endl;printer.print(std::string("Right Value Text"));std::cout << "打印字面量:" << std::endl;printer.print("Literal Text");}// 測試4:容器轉發std::cout << "\n4. 測試容器轉發:" << std::endl;{Container<Message> container;std::string item1 = "First";std::cout << "添加左值:" << std::endl;container.add(item1);std::cout << "添加右值:" << std::endl;container.add(std::string("Second"));std::cout << "添加字面量:" << std::endl;container.add("Third");container.showAll();}return 0;
}

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

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

相關文章

C/C++ 和 OpenCV 來制作一個能與人對弈的實體棋盤機器人

項目核心架構 整個系統可以分為四個主要模塊&#xff1a; 視覺感知模塊 (Vision Perception Module): 任務: 使用攝像頭“看懂”棋盤。工具: C, OpenCV。功能: 校準攝像頭、檢測棋盤邊界、進行透視變換、分割 64 個棋盤格、識別每個格子上的棋子、檢測人類玩家的走法。 決策模…

SpringBoot擴展——日志管理!

Spring Boot擴展 在Spring Boot中可以集成第三方的框架如MyBatis、MyBatis-Plus和RabbitMQ等統稱為擴展。每一個擴展會封裝成一個集成&#xff0c;即Spring Boot的starter&#xff08;依賴組件&#xff09;。starter是一種非常重要的機制&#xff0c;不需要煩瑣的配置&#xf…

【JSON-To-Video】AI智能體開發:為視頻圖片元素添加動效(滑入、旋轉、滑出),附代碼

各位朋友們&#xff0c;大家好&#xff01; 今天要教大家如何在 JSON - To - Video 中為視頻內圖片元素添加滑入、旋轉、滑出的動效。 如果您還不會封裝制作自己的【視頻工具插件】&#xff0c;歡迎查看之前的教程&#xff01; AI智能體平臺&#xff0c;如何封裝自定義短視頻…

Spring Boot(九十二):Spring Boot實現連接不上數據庫就重啟服務

場景: 在線上部署時,若服務器因斷電等原因意外重啟,項目及其依賴的數據庫服務通常需要配置為自動啟動。此時,如果數據庫服務啟動較慢或失敗,Spring Boot 項目會因無法建立數據庫連接而啟動失敗。 需求: 為確保項目啟動成功,需要讓 Spring Boot 項目等待數據庫服務完全就…

Debian配置Redis主從、哨兵

前言 Redis的下載安裝可參考Centos安裝配置Redis6.x&#xff0c;Centos和Debian的步驟基本類似&#xff0c;或自行在網上搜索相關資料 注意&#xff1a;遠程連接需放開相應端口 主從 搭建一個一主二從的主從模式 處理conf文件 #進入redis所在目錄 cd /tools/redis/redis6 …

虛實交融:數字孿生如何重塑交通與公路勘察設計的未來

當每一條道路、每一座橋梁、每一盞信號燈都在數字世界獲得“永生副本”&#xff0c;交通系統從被動響應邁入主動預演的紀元 一、數字孿生的核心定義&#xff1a;超越鏡像的動態認知引擎 數字孿生&#xff08;Digital Twin&#xff09;并非簡單的三維可視化模型&#xff0c;而是…

vector模擬實現中的迭代器失效問題

首先來看一組代碼&#xff1a; iterator insert(iterator pos, const T& x) {// 擴容if (_finish _end_of_storage){size_t len pos - _stare;reserve(capacity() 0 ? 4 : capacity() * 2);pos _stare len;}iterator end _finish - 1;while (end > pos){*(end…

java 設計模式_行為型_22模板模式

22.模板模式 模板方法&#xff08;Template Method&#xff09;作為Java的設計模式之一&#xff0c;一個詞概括其優勢特點那就是&#xff1a;抽象步驟 首先我們應該抽出共通的東西做一個父類&#xff08;Base類&#xff09;&#xff0c;其次具體的蛋糕制作由子類進一步實現&…

隨記:在springboot中websocket的使用

我現在有兩種方法 第一種&#xff1a;使用java封裝的這個包下的javax.websocket.* 先配置這個配置類 import com.alibaba.nacos.common.utils.CollectionUtils; import org.springframework.stereotype.Component;import javax.websocket.HandshakeResponse; import javax.w…

技術文章大綱:SpringBoot自動化部署實戰

技術文章大綱&#xff1a;SpringBoot自動化部署實戰 概述 自動化部署的背景與意義SpringBoot在現代化部署中的優勢常見自動化部署工具與方案概覽&#xff08;Jenkins、Docker、K8s等&#xff09; 環境準備 基礎工具要求&#xff1a;JDK、Maven/Gradle、Git服務器環境配置&a…

FastAdmin按鈕類功能全解析 class 屬性定義不同的交互行為

在 FastAdmin 中&#xff0c;超鏈接的 class 屬性用于定義不同的交互行為和樣式。以下是常見 class 配置的用途和區別&#xff1a; btn-dialog 用于觸發彈出對話框行為。點擊帶有此 class 的鏈接或按鈕時&#xff0c;FastAdmin 會自動加載指定的 URL 內容并在模態框中顯示。通…

python3字典對象實現解析

文章目錄 前言Raymond的方案字典結構字典創建字典插入插入空字典PyDictKeysObject的創建設置索引存儲entry 插入非空字典調整大小字典查找聯合字典插入 字典查詢字典刪除 前言 本來以為python字典的實現就是一個哈希表的普通實現&#xff0c;所以在學習基本類型時沒去仔細研究…

Word2Vec介紹

前言 當今的大語言模型非常智能&#xff0c;但是你有沒有想過這些事情&#xff1a; 機器是怎么理解“國王”和“王后”之間的關系&#xff1f; “貓”和“狗”是怎么在 AI 中“相似以及區分”的&#xff1f; 文本又是怎么變成模型能讀懂的數字&#xff1f; 這一切&#xf…

Rsync+sersync實現數據實時同步(小白的“升級打怪”成長之路)

目錄 一、rsync部署 push推數據 1、編寫rsync配置文件 2、備份測試 3、檢驗結果 二、rsyncsersync 實現數據實時同步 1、安裝sersync服務 2、檢驗結果 pull拉取數據 1、編寫rsync配置文件 2、檢驗結果 三、腳本編寫 1、客戶端腳本編寫 2、服務器腳本編寫 一、rsy…

用 python 開發一個可調用工具的 AI Agent,實現電腦配置專業評價

在人工智能時代&#xff0c;AI Agent憑借其強大的任務處理能力&#xff0c;逐漸成為開發人員手中的得力工具。今天&#xff0c;我們就來一起動手&#xff0c;用Python打造一個能夠調用工具的AI Agent&#xff0c;實現根據電腦信息對電腦配置進行專業評價的功能。 一、項目創建…

WSL 安裝使用和常用命令

參考官方使用說明&#xff1a; https://learn.microsoft.com/zh-cn/windows/wsl/ 安裝wsl: wsl --install --no-distribution --no-distribution&#xff1a;安裝 WSL 時不要安裝分發版 更新 wsl: wsl --update 設置wsl 默認版本&#xff1a; wsl --set-default-version <…

720全景VR拍攝制作實戰教程

720全景VR拍攝制作實戰教程 720全景VR拍攝制作是近年來興起的一種沉浸式影像制作技術。它通過多角度拍攝&#xff0c;并將畫面拼接成一個全景視角&#xff0c;使觀眾獲得身臨其境的觀看體驗。本教程將帶你從準備階段到拍攝階段&#xff0c;再到后期處理階段&#xff0c;一步步…

什么真正的云原生開發?如何區別本地開發后部署到云端?

以下是關于云原生開發的深度解析&#xff0c;以及與本地開發后遷移上云的本質區別&#xff1a; 一、真正的云原生開發&#xff1a;從理念到實踐的全面革新 1. 定義與核心思想 云原生開發是一種以云計算能力為核心的架構設計和開發方法論&#xff0c;其本質是讓應用從誕生之初…

從代碼學習深度學習 - 詞的相似性和類比任務 PyTorch版

提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔 文章目錄 前言加載預訓練詞向量TokenEmbedding 類詳解預訓練詞向量簡介 (GloVe)具體含義總結建議應用預訓練詞向量詞相似度knn 函數get_similar_tokens 函數相似詞查找示例詞類比get_analogy 函數詞類比任務…

ubuntu 22.04 安裝部署elk(elasticsearch/logstash/kibana) 7.10.0詳細教程

安裝部署elk7.10.0詳細教程 一、安裝jdk 11環境二、安裝elasticsearch 7.10.0三、安裝kibana 7.10.0四、安裝logstash 7.10.0五、安裝ik7.10.0分詞六、開啟安全功能1. 開啟用戶名密碼登錄2. 開啟es安全加密通信3. 開啟Kibana安全功能 七、注意事項和常見錯誤八、其它操作及命令…