Android 13 VSYNC重學習

Android 13 VSYNC重學習



引言

學無止境,一個字干就完事!

源碼參考基于Android 13 aosp!




一. Android VSync模塊開胃菜

在開始正式的分析之前,我們先簡單對Android的Vsync模塊簡單介紹下,如下圖所示,其中:

  • HW_VSync是由屏幕產生的脈沖信號,用于控制屏幕的刷新
  • VSync-app和VSync-sf統稱為軟件VSync,它們是由SurfaceFlinger通過模擬硬件VSync而產生的VSync信號量,再分發給app和sf用來控制它們的合成節奏

image



image





二. Android VSync小結

這里有幾點需要補充:

  • VSync-sf是沒有對應的EventThread和DispSyncSource

  • VSync-app和VSync-appSf各自都有對應的EventThread和DispSyncSource

  • VSync-sf和VSync-app以及Sync-appSf通過Scheduler的成員mVsyncSchedule指向的VSyncDispatchTimerQueue實例對象關聯



Android下VSync設計,牽涉的核心關系圖如下:

image


2.1 VSync信號的分類

VSync信號分為兩種:硬件VSync信號HW-VSync和軟件VSync信號SW-VSync。SW-VSync信號由SW-VSync模型產生。HW-VSync信號負責對SW-VSync模型進行校準。


2.2 HW-Vsync信號的開啟

三種場景下會開啟硬件VSync信號HW-VSync會對軟件VSync信號SW-VSync進行校準

  • SurfaceFlinger初始化。

  • 連續兩次請求VSync-app信號的時間間隔超過750ms。

  • SurfaceFlinger合成后,添加FenceTime到VSyncTracker中導致模型計算誤差過大。


2.3 SW-VSync模型與計算

谷歌官方采用一元線性回歸分析預測法(最小二乘法),通過采樣的HW-VSync信號樣本(屏幕刷新率),計算對應的SW-VSync信號周期。最終得到一條y=bx+a的擬合曲線。其中,b稱為回歸系數,a稱為截距。SW-VSync模型就是這這條曲線的回歸系數和截距。


2.4 SW-VSync信號的分類

SW-VSync信號也分為兩種,VSync-sf信號和Vsync-app信號。這兩個信號,各司其職:

  • VSync-sf信號用于控制SurfaceFlinger的Layer合成
    - VSync-app信號用于控制App渲染UI

VSync-sf信號和VSync-app信號是在SW-VSync信號的基礎上通過疊加不同的偏移量產生,這些偏移量被稱為VSync相位偏移。由于偏移量不同VSync-sf信號和VSync-app信號的回調時機也不同。




三. VSync-sf的申請和分發

VSync-sf用于控制SurfaceFlinger合成和渲染一幀圖像。當SurfaceFlinger上幀時(BufferQueue中有新的GraphicBuffer),SurfaceFlinger會觸發MessageQueue的scheduleFrame方法。接下來我們看下,VSync-sf是如何完成從申請到分發的流程。

3.1 VSync-sf的申請

SurfaceFlinger::scheduleCommit(...)//請求上幀mScheduler->scheduleFrame()//MessageQueue.cppmVsync.registration->schedule()//這里的registration實現是VSyncCallbackRegistration,定義在Scheduler/VSyncDispatchTimerQueue.cppmDispatch.get().schedule()//這里的mDispatch指向VSyncDispatchTimerQueue對象/*** @brief * * @param token * @param scheduleTiming * @return ScheduleResult * 1)根據CallbackToken找到所有滿足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中對外部VSync信號請求的封裝。* 2)遍歷調用VSyncDispatchTimerQueue的schedule方法,計算下一次VSync信號的發送時間。* 3)對發射時間進行定時,等待下一次VSync信號的發送*/
ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token,ScheduleTiming scheduleTiming) {...//根據CallbackToken找到所有滿足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中對外部VSync信號請求的封裝。auto it = mCallbacks.find(token);auto& callback = it->second;//遍歷調用VSyncDispatchTimerQueue的schedule方法,計算下一次VSync信號的發送時間result = callback->schedule(scheduleTiming, mTracker, now);//對發射時間進行定時,等待下一次VSync-sf信號的發送rearmTimerSkippingUpdateFor(now, it);VSyncDispatchTimerQueue::setTimer()void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t /*now*/) {mIntendedWakeupTime = targetTime;mTimeKeeper->alarmAt(std::bind(&VSyncDispatchTimerQueue::timerCallback, this),mIntendedWakeupTime);mLastTimerSchedule = mTimeKeeper->now();
}     /*** @brief * 1)遍歷CallbackMap找到達到喚醒時間的VSyncDispatchTimerQueueEntry,并封裝成Invocation,加入Invocation列表。* 2)遍歷Invocation列表,通過Invocation獲取VSyncDispatchTimerQueueEntry,并調用VSyncDispatchTimerQueueEntry的callback方法分發VSync信號。*///Scheduler/VSyncDispatchTimerQueue.cpp
void VSyncDispatchTimerQueue::timerCallback() {struct Invocation {std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;nsecs_t vsyncTimestamp;nsecs_t wakeupTimestamp;nsecs_t deadlineTimestamp;};std::vector<Invocation> invocations;{std::lock_guard lock(mMutex);auto const now = mTimeKeeper->now();mLastTimerCallback = now;for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {auto& callback = it->second;auto const wakeupTime = callback->wakeupTime();if (!wakeupTime) {continue;}auto const readyTime = callback->readyTime();auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast<nsecs_t>(0));if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) {callback->executing();invocations.emplace_back(Invocation{callback, *callback->lastExecutedVsyncTarget(),*wakeupTime, *readyTime});}}mIntendedWakeupTime = kInvalidTime;rearmTimer(mTimeKeeper->now());}for (auto const& invocation : invocations) {invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,invocation.deadlineTimestamp);}
}}

3.2 VSync-sf的分發

那么VSync-df的callback是怎么注冊到VSyncDispatchTimerQueue的呢,這個我們看下:

SurfaceFlinger::initScheduler(...)mScheduler->initVsync(...)//實現在Scheduler/MessageQueue.cpp中mVsync.registration = std::make_unique<scheduler::VSyncCallbackRegistration>(dispatch,std::bind(&MessageQueue::vsyncCallback, this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3),"sf");//這里的dispatch指向VSyncDispatchTimerQueue//Scheduler/VSyncDispatchTimerQueue.cppVSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,VSyncDispatch::Callback callback,std::string callbackName): mDispatch(dispatch),mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),mValidToken(true) {}       VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(Callback callback, std::string callbackName) {std::lock_guard lock(mMutex);return CallbackToken{//最終注冊到了mCallbacks中mCallbacks.emplace(++mCallbackToken,std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),std::move(callback),mMinVsyncDistance)).first->first};
}        

所以最后VSync-sf的分發會調用到MessageQueue::vsyncCallback中,我們看下它的實現:

//Scheduler/MessageQueue.cpp
MessageQueue::vsyncCallback(...)mHandler->dispatchFrame(vsyncId, vsyncTime)mQueue.mLooper->sendMessage(this, Message())//Handle的handleMessage接收前面發過來的消息
void MessageQueue::Handler::handleMessage(const Message&) {mFramePending.store(false);const nsecs_t frameTime = systemTime();auto& compositor = mQueue.mCompositor;//這里的compositor實現類是SurfaceFlingerif (!compositor.commit(frameTime, mVsyncId, mExpectedVsyncTime)) {return;}compositor.composite(frameTime, mVsyncId);compositor.sample();
}



四. VSync-app的申請和分發

在開始后續的章節編寫前,我們先重點申明下:

VSync-app用于控制App的UI渲染

VSync-app用于控制App的UI渲染

VSync-app用于控制App的UI渲染


4.1 VSync-app的申請

當Choreographer通過FrameDisplayEventReceiver調用scheduleVsync方法時,會觸發VSync-app信號的申請。在FrameDisplayEventReceiver的scheduleVsync方法中,會調用nativeScheduleVsync方法。

image

FrameDisplayEventReceiver的nativeScheduleVsync方法對應的native實現為android_view_DisplayEventReceiver的nativeScheduleVsync函數。

在nativeScheduleVsync函數中,主要做了兩件事:

  • 取native層的DisplayEventDispatcher。

  • 調用DisplayEventDispatcher的scheduleVsync方法,請求VSync信號。

image

在DisplayEventDispatcher的scheduleVsync方法中,會調用DisplayEventReceiver的requestNextVsync方法。

image

在DisplayEventReceiver的requestNextVsync方法中,會調用IDisplayEventConnection的requestNextVsync方法。

image

IDisplayEventConnection是一個Binder類,對應bn端的實現類為BnDisplayEventConnection。而EventThreadConnection繼承自BnDisplayEventConnection,因此實際調用的是EventThreadConnection的requestNextVsync方法。

image

在EventThreadConnection的requestNextVsync方法中,會調用EventThread的requestNextVsync方法。

image

在EventThread的requestNextVsync方法中,主要做了三件事:

  • 開啟硬件VSync信號對軟件VSync信號進行校準。

  • 標記EventThreadConnection的vsyncRequest,為后續信號分發做準備。

  • 喚起EventThread對應的線程繼續執行VSync信號的分發。

image

//Scheduler/EventThread.cpp
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {if (connection->resyncCallback) {/*** @brief * 調用到Scheduler::resync* 開啟硬件Vsync信號對軟件Vsync信號進行校準*/connection->resyncCallback();}std::lock_guard<std::mutex> lock(mMutex);if (connection->vsyncRequest == VSyncRequest::None) {connection->vsyncRequest = VSyncRequest::Single;mCondition.notify_all();//喚起EventThread中的線程} else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {connection->vsyncRequest = VSyncRequest::Single;}
}

在EventThread的threadMain中,會通過VSyncCallbackRegistration請求或取消VSync信號。

如果是請求VSync信號,會調用VSyncCallbackRegistration的schedule方法。在VSyncCallbackRegistration的schedule方法,會調用VSyncDispatch的schedule方法。

image

void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {if (mState != nextState) {if (mState == State::VSync) {mVSyncSource->setVSyncEnabled(false);} else if (nextState == State::VSync) {mVSyncSource->setVSyncEnabled(true);}mState = nextState;}}

之后的流程與VSync-sf信號的申請流程相同。在VSyncDispatchTimerQueue的schedule方法中,會調用scheduleLocked方法。

在VSyncDispatchTimerQueue的scheduleLocked方法中,主要做了三件事:

  • 根據CallbackToken找到所有滿足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中對外部VSync信號請求的封裝。

  • 遍歷調用VSyncDispatchTimerQueue的schedule方法,計算下一次VSync信號的發送時間。

  • 對發射時間進行定時,等待下一次VSync信號的發送。

image


4.2 VSync-app的分發

當定時時間到達時,TimerKeeper會回調VSyncDispatchTimerQueue的timerCallback方法。

在VSyncDispatchTimerQueue的timerCallback方法方法中,主要做了兩件事:

  • 遍歷CallbackMap找到達到喚醒時間的VSyncDispatchTimerQueueEntry,并封裝成Invocation,加入Invocation列表。

  • 遍歷Invocation列表,通過Invocation獲取VSyncDispatchTimerQueueEntry,并調用VSyncDispatchTimerQueueEntry的callback方法分發VSync信號。

image

在VSyncDispatchTimerQueueEntry的callback方法中,會調用類型為CallbackRepeater::callbackk,然后在該方法中接著調用mCallback(vsyncTime, wakeupTime, readyTime)方法,而這里的mCallback(指向DispSyncSource::onVsyncCallback,最后回調EventThread的onVSyncEvent方法。

對于上述的分發流程是不是還有點懵逼,我們反過來看看VSync-app分發的注冊,其核心是DispSyncSource和EventThread以及VSyncDispatchTimerQueue的各種回調callback流程:


//Scheduler/VSyncDispatchTimerQueue.cpp
VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(Callback callback, std::string callbackName) {std::lock_guard lock(mMutex);return CallbackToken{mCallbacks.emplace(++mCallbackToken,std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),std::move(callback),mMinVsyncDistance)).first->first};
}//Scheduler/VSyncDispatchTimerQueue.cpp
VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,VSyncDispatch::Callback callback,std::string callbackName): mDispatch(dispatch),mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),mValidToken(true) {}//Scheduler/DispSyncSource.cpp
class CallbackRepeater {
public:CallbackRepeater(VSyncDispatch& dispatch, VSyncDispatch::Callback cb, const char* name,std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,std::chrono::nanoseconds notBefore): mName(name),mCallback(cb),//VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex);mRegistration(dispatch,std::bind(&CallbackRepeater::callback, this, std::placeholders::_1,std::placeholders::_2, std::placeholders::_3),mName),mStarted(false),mWorkDuration(workDuration),mReadyDuration(readyDuration),mLastCallTime(notBefore) {}~CallbackRepeater() {std::lock_guard lock(mMutex);mRegistration.cancel();}void start(std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) {std::lock_guard lock(mMutex);mStarted = true;mWorkDuration = workDuration;mReadyDuration = readyDuration;auto const scheduleResult = mRegistration.schedule({.workDuration = mWorkDuration.count(),.readyDuration = mReadyDuration.count(),.earliestVsync = mLastCallTime.count()});LOG_ALWAYS_FATAL_IF((!scheduleResult.has_value()), "Error scheduling callback");}void stop() {std::lock_guard lock(mMutex);LOG_ALWAYS_FATAL_IF(!mStarted, "DispSyncInterface misuse: callback already stopped");mStarted = false;mRegistration.cancel();}void dump(std::string& result) const {std::lock_guard lock(mMutex);const auto relativeLastCallTime =mLastCallTime - std::chrono::steady_clock::now().time_since_epoch();StringAppendF(&result, "\t%s: ", mName.c_str());StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ",mWorkDuration.count() / 1e6f, mReadyDuration.count() / 1e6f);StringAppendF(&result, "%.2fms relative to now (%s)\n", relativeLastCallTime.count() / 1e6f,mStarted ? "running" : "stopped");}private:void callback(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {{std::lock_guard lock(mMutex);mLastCallTime = std::chrono::nanoseconds(vsyncTime);}mCallback(vsyncTime, wakeupTime, readyTime);{std::lock_guard lock(mMutex);if (!mStarted) {return;}auto const scheduleResult =mRegistration.schedule({.workDuration = mWorkDuration.count(),.readyDuration = mReadyDuration.count(),.earliestVsync = vsyncTime});LOG_ALWAYS_FATAL_IF(!scheduleResult.has_value(), "Error rescheduling callback");}}const std::string mName;scheduler::VSyncDispatch::Callback mCallback;mutable std::mutex mMutex;VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex);bool mStarted GUARDED_BY(mMutex) = false;std::chrono::nanoseconds mWorkDuration GUARDED_BY(mMutex) = 0ns;std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex) = 0ns;std::chrono::nanoseconds mLastCallTime GUARDED_BY(mMutex) = 0ns;
};mAppConnectionHandle =mScheduler->createConnection("app" .....)Scheduler::createConnection()auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration)return std::make_unique<scheduler::DispSyncSource>(mVsyncSchedule->getDispatch(),mVsyncSchedule->getTracker(), workDuration,readyDuration, traceVsync, name);//std::unique_ptr<CallbackRepeater> mCallbackRepeater;mCallbackRepeater =std::make_unique<CallbackRepeater>(vSyncDispatch,std::bind(&DispSyncSource::onVsyncCallback, this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3),name, workDuration, readyDuration,std::chrono::steady_clock::now().time_since_epoch());      mVSyncSource->setCallback(this);//為DispVsyncSource設置回調void DispSyncSource::setCallback(VSyncSource::Callback* callback) {std::lock_guard lock(mCallbackMutex);mCallback = callback;}               //最終整理出來的Vsync-app分發流程為,各種彎彎繞繞:VSyncDispatchTimerQueue::timerCallback()//Scheduler/VSyncDispatchTimerQueue.cppinvocation.callback->callback(...)//這里的callback指向VSyncDispatchTimerQueueEntry::callback,Scheduler/VSyncDispatchTimerQueue.cppmCallback(vsyncTimestamp, wakeupTimestamp, deadlineTimestamp)//這里的 mCallback指向CallbackRepeater::callback,實現在Scheduler/DispSyncSource.cpp mCallback(vsyncTime, wakeupTime, readyTime)//這里的callback指向DispSyncSource::onVsyncCallback。是現在Scheduler/DispSyncSource.cppcallback = mCallback;callback->onVSyncEvent(targetWakeupTime, {vsyncTime, readyTime})//這里的callback指向EventThread::onVSyncEvent

在EventThread的onVSyncEvent方法中,主要做了三件事:

  • 調用makeVSync函數,創建Event。

  • 將Event加入到vector<DisplayEventReceiver::Event> 中。

  • 喚醒等待線程,執行threadMain方法。

image

void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {std::lock_guard<std::mutex> lock(mMutex);LOG_FATAL_IF(!mVSyncState);//包裝為DisplayEventReceiver::Event對象,存入mPendingEvents尾部mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,vsyncData.expectedPresentationTime,vsyncData.deadlineTimestamp));//喚醒線程mCondition.notify_all();
}

我們接下來看EventThread是如何處理分發事件的:

//Scheduler/EventThread.cpp
void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {DisplayEventConsumers consumers;while (mState != State::Quit) {std::optional<DisplayEventReceiver::Event> event;// Determine next event to dispatch.if (!mPendingEvents.empty()) {event = mPendingEvents.front();mPendingEvents.pop_front();        ...}// Find connections that should consume this event.auto it = mDisplayEventConnections.begin();while (it != mDisplayEventConnections.end()) {if (const auto connection = it->promote()) {vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;//用來在任務的循環執行中保存當前Vsync信號的消費者if (event && shouldConsumeEvent(*event, connection)) {consumers.push_back(connection);//這里的consumers就是待分發的目標}++it;} else {it = mDisplayEventConnections.erase(it);}}        /*** @brief * 在該方法中,會循環分發信號,主要做了五件事情* 1) 從Vsync信息隊列中獲取消息* 2)收集監聽Vsync信號的EventThreadConnection,并加入到consumers中* 3) 調用dispatchEvent方法來分發Vsync信號* 4)計算當前狀態,根據狀態請求或取消下一次VSync信號* 5)如果沒有Vsync信號需要分發,線程進入等待狀態*/if (!consumers.empty()) {dispatchEvent(*event, consumers);consumer->postEvent(copy)DisplayEventReceiver::sendEvents(...)consumers.clear();}        

最終VSync-app分發的事件會被Choreographer模塊接收,開始安排應用相關的渲染UI邏輯!




Andoid SurfaceFlinger(二) VSYNC的開始,連續,結束
VSYNC研究-最后的窗戶紙
Android 12(S) 圖像顯示系統 - SurfaceFlinger之VSync-上篇(十六)
Android 12(S) 圖像顯示系統 - SurfaceFlinger 之 VSync - 中篇(十七)
深度詳解 Android S(12.0)屏幕刷新機制之 Choreographer
View繪制流程3-Vsync信號是如何發送和接受的
Android R Vsync相關梳理
顯示框架之深入Vsync原理
App/Sf的Vsync部分源碼流程結合perfetto/systrace分析
Android-View繪制原理(02)-VSync原理之SurfaceFlinger篇
一文搞定Android VSync來龍機制去脈
VSync信號系統與SurfaceFlinger
SurfaceFlinger-Vsync信號
Android VSync事件分發過程源碼分析

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

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

相關文章

【Java面試】一、Redis篇(上)

文章目錄 0、準備1、緩存穿透&#xff1a;不存在的key2、緩存擊穿&#xff1a;熱點key過期3、緩存雪崩&#xff1a;大批key同時過期4、雙寫一致性4.1 要求高一致性4.2 允許一定的一致延遲 5、面試 0、準備 Redis相關概覽&#xff1a; 以簡歷上所列的項目為切入點&#xff0c;展…

Steamdeck使用Windows系統游玩雪地奔馳時閃退問題解決方法

我非常喜歡雪地奔馳這款游戲&#xff0c;買sd的一部分也是為了它。可在我打開這個游戲時&#xff0c;游戲發生閃退問題。查閱了網絡各個途徑&#xff0c;基本沒有解決方法。因此我自己分析終于解決該問題。以下是我解決問題的思路&#xff0c;僅供記錄參考&#xff1a; 游戲在崩…

2024提升數字思維能力加快企業數字化轉型(74頁PPT)

方案介紹&#xff1a; 本報告的價值在于為企業提供了一套系統的提升數字思維能力、加快數字化轉型的理論框架和實踐指南。通過本報告的學習和應用&#xff0c;企業可以更加清晰地認識到數字化轉型的重要性和緊迫性&#xff0c;明確自身在數字化轉型中的優勢和不足&#xff0c;并…

已解決java.nio.charset.CoderMalfunctionError: 編碼器故障錯誤的正確解決方法,親測有效!!!

已解決java.nio.charset.CoderMalfunctionError: 編碼器故障錯誤的正確解決方法&#xff0c;親測有效&#xff01;&#xff01;&#xff01; 目錄 問題分析 報錯原因 解決思路 解決方法 確認與檢查字符集 驗證輸入數據 嘗試使用不同字符集 更新或更換編碼器/解碼器版本…

ES升級--02--kibana安裝與啟動

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 Kibana官網文檔https://www.elastic.co/guide/cn/kibana/current/targz.html 1.官網下載https://www.elastic.co/cn/downloads/past-releases#kibana 2.解壓軟件3.配…

python四舍五入(round精度不夠,有時不能實現四舍五入)

Python 所有文章傳送門【Python】所有文章傳送門 目錄 簡述 / 前言1. Python 實驗2. 自定義函數3. 總結 簡述 / 前言 最近心血來潮&#xff0c;剛復習到折半插入排序時&#xff0c;發現算法的mid&#xff08;中間點&#xff09;選擇的公式是&#xff1a;(low high)/2&#xf…

基于VMware安裝Linux虛擬機

1.準備Linux環境 首先&#xff0c;我們要準備一個Linux的系統&#xff0c;成本最低的方式就是在本地安裝一臺虛擬機。為了統一學習環境&#xff0c;不管是使用MacOS還是Windows系統的同學&#xff0c;都建議安裝一臺虛擬機。 windows采用VMware&#xff0c;Mac則采用Fusion …

使用Spring Boot編寫的小項目

加法計算器 前端代碼 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…

若依跳轉(新增)頁面,在菜單中不顯示的頁面

在router.js文件中 跳轉方式 this.$router.push(/monitor/b/b)

有限元之有限元法的實現

目錄 一、單元剛度矩陣及單元荷載 二、總剛度矩陣及總荷載的合成 三、邊界條件處理 四、算例實現 4.1 C代碼 4.2 計算結果 五、結論 前三節我們介紹了有限元的基本概念、變分理論及有限元空間的構造&#xff0c;本節我們探討如何實現有限元法。我們繼續以二維橢圓型方程…

以太坊現貨ETF獲批:引發ETH價格暴漲,市場熱議達到高潮

2024年5月24日&#xff0c;北京時間&#xff0c;以太坊現貨ETF正式獲得美國證券交易委員會&#xff08;SEC&#xff09;的批準&#xff0c;成為繼比特幣之后&#xff0c;美國主權政府承認的又一加密貨幣基金產品。這一意外的利好消息引發了加密貨幣市場的狂歡&#xff0c;以太坊…

JavaWeb開發 2.Web開發 Web前端開發 ①介紹

內心一旦平靜&#xff0c;外界便鴉雀無聲 —— 24.5.27 一、初識Web前端 網頁有哪些部分組成? 文字、圖片、音頻、視頻、超鏈接 ...網頁&#xff0c;背后的本質是什么? 前端代碼前端的代碼是如何轉換成用戶眼中的網頁的? 通過瀏覽器轉化(解析和渲染)成用戶看…

dx11硬件解碼傳遞給opencl并行處理

directx11 解碼 使用ffmpeg進行directx11 解碼 將解碼后的NV12格式數據從D3D11 Texture中通過OpenCL處理需要經過幾個步驟&#xff1a;首先&#xff0c;確保D3D11 Texture正確設置并與OpenCL上下文關聯&#xff1b;然后&#xff0c;將NV12數據分兩個步驟處理&#xff08;Y平面…

調試面對面翻譯小程序

調試面對面翻譯小程序 文章目錄 調試面對面翻譯小程序預覽1.拉取項目2.在微信開發者工具打開使用 微信版本要求微信同聲傳譯插件支持功能 此demo用于學習 預覽 1.拉取項目 git clone https://github.com/Tencent/Face2FaceTranslator或者&#xff08;加速鏡像&#xff09; git …

Warning: Each child in a list should have a unique “key“ prop.

問題描述&#xff1a; 使用ProTable的時候&#xff0c;報錯如下 原因分析&#xff1a; 根據報錯內容可以分析出&#xff0c;表格數據缺少唯一key&#xff0c; <PaginationTablecolumns{columns}pagination{{pageSize: 10,current: 1,showSizeChanger: true,showQuickJum…

kafka 可以脫離 zookeeper 單獨使用嗎?為什么?

Kafka是一個分布式的流式處理平臺&#xff0c;它依賴于Zookeeper來管理集群元數據、選舉Leader以及故障恢復。在Kafka集群中&#xff0c;Zookeeper負責保存和維護分布式系統的信息。 雖然理論上可以將Kafka與Zookeeper分開&#xff0c;但實際上&#xff0c;Kafka在設計時就與Z…

JavaScript中的相等操作符(== vs ===)選擇指南

在使用JavaScript進行比較時,我們經常會遇到相等操作符 == 和嚴格相等操作符 ===。本文將深入探討這兩者之間的區別,并說明在何種情況下應使用 === 而不是 ==。 相等操作符(==) 相等操作符 == 會在進行比較之前對其兩個操作數進行必要的類型轉換。這意味著即使兩個操作數…

網絡安全行為可控定義以及表現內容簡述

在數字化快速發展的今天&#xff0c;網絡安全已成為國家和企業不可或缺的防線。據統計&#xff0c;網絡攻擊事件頻發&#xff0c;給全球經濟帶來了巨大損失。因此&#xff0c;確保網絡安全行為可控顯得尤為重要。今天我們來聊聊網絡安全行為可控定義以及表現內容。 網絡安全行為…

摸魚大數據——Hive表操作——分區表

1、介紹 特點: 分區表會在HDFS上產生目錄。查詢數據的時候使用分區字段篩選數據&#xff0c;可以避免全表掃描&#xff0c;從而提升查詢效率 注意: 如果是分區表&#xff0c;在查詢數據的時候&#xff0c;如果沒有使用分區字段&#xff0c;它回去進行全表掃描&#xff0c;會降低…

說一下 ACID 是什么?

ACID 是數據庫事務的四個特性的首字母縮寫&#xff0c;包括原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔離性&#xff08;Isolation&#xff09;和持久性&#xff08;Durability&#xff09;。 原子性&#xff08;Atomicity&…