📜 [專欄新文章] 區塊鏈管線化的效能增進與瓶頸
✍️ Ping Chen
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
使用管線化(Pipeline)技術可以提升區塊鏈的處理效能,但也可能會產生相應的代價。
Photo by tian kuan on Unsplash
區塊鏈的擴容方案
說到區塊鏈的效能問題,目前討論度最高的應該是分片(sharding)技術,藉由將驗證者分成多組的方式,可以同時分別處理鏈上的交易需求,即使單分片效能不變,總交易量可以隨著分片/驗證者集的數量線性增加。
除了分片,另一個常用來提升程式效能的方案是將計算步驟拆解,以流水線的方式將複雜的運算攤平,降低系統的閒置時間,並大幅提升工作效率。為了達到管線化預期的目的,會需要先知道系統的瓶頸在哪。
區塊鏈的效能瓶頸
熟悉工作量證明設計哲學的人應該會知道,區塊鏈之所以需要挖礦,並不是為了驗證交易的正確性,而是要決定交易的先後順序,從而避免雙花和帳本分裂的發生。可以說,區塊鏈使用低效率的單線程設計,並付給礦工高額的成本,都只為了一件事,就是對交易的全局排序產生共識。
在這樣的基礎之上,區塊鏈在一段時間內可以處理的交易數量是有限的,這之中包含許多方面的限制,包括 CPU 效能、硬碟空間、網路速度等。其中,關於 TPS(每秒交易數) 提升和對硬體的要求大致上是線性增加的,但在設計共識演算法時,通訊複雜度常是平方甚至三次方的關係。
以現在的目標 TPS 來說,處理交易和生成一個合法的區塊並不困難,只是因為區塊鏈的特性,新區塊需要透過洪水法的方式擴散到全網路,每個節點在收到更新請求的時候都要先執行/驗證過區塊內的交易,等於整個廣播的延時會是「驗證區塊時間×經過的 hop 數量」這麼多。似乎網路越分散、節點越多,我們反而會需要降低計算量,以免讓共識不穩定。
管線化的共識機制
使用權益證明取代工作量證明算是行業發展的趨勢,除了環保或安全這些比較顯然的好處之外,權益證明對產生共識的穩定性也很有幫助。首先,權益證明在同一時間參與共識的節點數是已知的,比較容易控制數量級的邊界;其次,權益證明的出塊時間相較工作量證明固定很多,可以降低計算資源不足或閒置的機率。
相較於工作量證明是單一節點出塊,其餘節點驗證,權益證明的出塊本身就需要很多節點共同參與,瓶頸很像是從驗證轉移到通訊上。
以 PBFT 為例,每次產新區塊都需要經過 pre-prepare, prepare, commit 三個階段,你要對同意驗證的區塊簽名,還要對「你有收到某人的簽名」這件事簽名,再對「你有收到 A 說他有收到 B 的簽名」這件事簽名,過程中會有很多簽名飛來飛去,最後才能把一個區塊敲定。
為了降低每兩個區塊間都需要三輪簽名造成的延遲,後來的共識演算法包括 HotStuff 和 Casper FFG 採用了管線化的區塊驗證過程。也就是對區塊 T 的 pre-prepare 同時是對 T-1 的 prepare 和對 T-2 的 commit。再加上簽名聚合技術,出塊的開銷在複雜度等級和係數等級都降低許多。
然而,要保持管線化的區塊生產順利,需要驗證者集合固定不變,且網路通訊狀況良好。如果會經常更動驗證者集合或變換出塊的領導者,前後區塊間的相依性會是個大問題,也就是 T 的驗證者集合取決於 T-1 裡有沒有會導致刪除或新增驗證者的交易,T-1 的合法性又相依於 T-2,以此類推。
當激烈的分叉出現的時候,出塊跟共識的流水線式耦合就從優雅變成災難了。為了避免這種災難,更新的共識演算法會限制驗證者變更的時機,有些叫 epoch 有些叫 checkpoint,每隔一段時間會把前面的區塊徹底敲定,才統一讓驗證者加入或退出。到這些檢查點的時候,出塊的作業流程就會退化成原本的三階段驗證,但在大部分時候還是有加速的效果。
管線化的狀態更新
另一個可以用管線化加速的是區塊鏈的狀態更新。如前所述,現在公鏈的瓶頸在於提高 TPS 會讓區塊廣播變慢,進而導致共識不穩定,這點在區塊時間短的以太坊上尤其明顯。可是如果單看執行一個區塊內的交易所花的時間的話,實際上是遠遠低於區塊間隔的。
只有在收到新區塊的時候,節點才會執行狀態轉移函數,並根據執行結果是否合法來決定要不要把區塊資訊再廣播出去。不過其實只要給定了交易集合,新的狀態 s’ = STF(s, tx) 應該是確定性的。
於是我們有了一個大膽的想法:何不乾脆將交易執行結果移出共識外呢?反正只要大家有對這個區塊要打包哪些交易有共識,計算的結果完全可以當作業留給大家自己算吧。如果真的不放心,我們也可以晚點再一起對個答案,也就是把這個區塊執行後的新狀態根包在下個區塊頭裡面。
這就是對狀態更新的管線化,在區塊 T 中敲定交易順序但暫不執行,區塊 T+1 的時候才更新狀態(以及下一批交易)。這麼做的好處十分顯而易見,就是將原本最緊繃的狀態計算時間攤平了,從原本毫秒必爭的廣播期移出來,變成只要在下個塊出來之前算完就好,有好幾秒的時間可以慢慢來。新區塊在廣播的每個 hop 之間只要驗證交易格式合法(簽名正確,有足夠的錢付手續費)就可以放行了,甚至有些更激進的方案連驗簽名都省略了,如果真的有不合法交易混進去就在下個區塊處罰礦工/提案者便是。
把負擔最重的交易執行移出共識,光用想的就覺得效能要飛天,那代價呢?代價是區塊的使用程度會變得不穩定。因為我們省略了執行,所以對於一筆交易實際用掉多少 gas 是未知的。本來礦工會完整的執行所有交易,並盡可能的塞滿區塊空間,然而在沒有執行的情況下,只能以使用者設定的 gas limit 當作它的用量,能打包的交易會比實際的上限少。
緊接著,下一個問題是退費困難。如果我們仍然將沒用完的手續費退還給使用者,惡意的攻擊者可以透過發送 gas limit 超大,實際用量很小的交易,以接近零的成本「霸佔」區塊空間。所以像已故區塊鏈 DEXON 就直接取消 gas refund,杜絕濫用的可能。但顯然這在使用者體驗和區塊空間效率上都是次優的。
而最近推出的 smartBCH 嘗試擬了一套複雜的退款規則:交易執行後剩餘的 gas 如果小於 gas limit 的一半(代表不是故意的)就退款;如果剩餘量介於 50%-75% 可以退一半;超過 75% 推斷為惡意,不退款。乍看是個合理的方案,仔細一想會發現製造的問題似乎比解決的還多。無論如何,沒用掉的空間終究是浪費了,而根據殘氣比例決定是否退款也不會是個好政策,對於有條件判斷的程式,可能要實際執行才知道走哪條路,gas limit 一定是以高的情況去設定,萬一進到 gas 用量少的分支,反而會噴更多錢,怎麼想都不太合理。
安全考量,退費大概是沒希望了。不過呢,最近以太坊剛上線的 EIP1559 似乎給了一點方向,如果區塊的使用程度能以某種回授控制的方式調節,即使偶爾挖出比較空的區塊似乎也無傷大雅,也許能研究看怎麼把兩者融合吧。
管線化方案的發展性
考慮到以太坊已經堅定地選擇了分片的路線,比較激進的單鏈高 TPS 管線化改造方案應該不太有機會出線,不過管線化畢竟是種歷史悠久的軟體最佳化技巧,還是很有機會被使用在其他地方的,也許是 VDF 之於信標鏈,也許是 rollup 的狀態轉換證明,可以坐等開發者們表演。
倒是那些比較中心化的 EVM fork/sidechain,尤其是專門只 for DeFi 的鏈,管線化加速可以在不破壞交易原子性的前提下擴容,確實是有一些比分片優秀的地方可以說嘴,值得研究研究,但這就要看那些機房鏈們有沒有上進心,願不願意在分叉之餘也投資發展自己的新技術了。
給我錢
ping.eth
區塊鏈管線化的效能增進與瓶頸 was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
同時也有19部Youtube影片,追蹤數超過40萬的網紅糖餃子Sweet Dumpling,也在其Youtube影片中提到,Hello friends! Today we're going to share with you how to make a French style chocolate and vanilla cake: Chocolate Marble Cake (Marbré Au Chocolat). ...
「fork教學」的推薦目錄:
- 關於fork教學 在 Taipei Ethereum Meetup Facebook 的精選貼文
- 關於fork教學 在 Taipei Ethereum Meetup Facebook 的最佳解答
- 關於fork教學 在 吃貨愛健身教練coco Facebook 的精選貼文
- 關於fork教學 在 糖餃子Sweet Dumpling Youtube 的最佳貼文
- 關於fork教學 在 賴阿奇 Youtube 的最佳解答
- 關於fork教學 在 賴阿奇 Youtube 的最讚貼文
- 關於fork教學 在 Re: [系程] 教學: 簡介fork, exec*, pipe, dup2 - 看板b97902HW 的評價
- 關於fork教學 在 30 天精通Git 版本控管(28):了解GitHub 的fork 與pull request ... 的評價
- 關於fork教學 在 6.2 GitHub - 參與一個專案 - Git SCM 的評價
- 關於fork教學 在 【狀況題】怎麼跟上當初fork 專案的進度? - 為你自己學Git 的評價
- 關於fork教學 在 Git 版本控制系統- Fork 複製倉庫與Pull Request 請求合併 的評價
- 關於fork教學 在 [GIT101 心得/筆記] GitHub 操作-push , pull, clone , fork - Medium 的評價
- 關於fork教學 在 使用GitHub 更新Fork 專案的原始碼 - 不及格研究室 的評價
- 關於fork教學 在 Fork a repo - GitHub Docs 的評價
- 關於fork教學 在 Git + GitHub 版本控制教學( 5) - 使用GitHub 與團隊合作 的評價
- 關於fork教學 在 url安裝fork教學 的評價
- 關於fork教學 在 同步github/gitlab fork 出來的repository - 健忘的工程師筆記本 的評價
- 關於fork教學 在 github fork and pull request,大家都在找解答 訂房優惠報報 的評價
- 關於fork教學 在 GitHub 協作流程快速教學 的評價
- 關於fork教學 在 使用GitHub 更新Fork 專案的原始碼 - 隱龍窟 的評價
- 關於fork教學 在 Git & GitHub 教學手冊- Git 環境教學 - W3HexSchool - 六角學院 的評價
- 關於fork教學 在 【轉】github上的FORK是什麼意思以及同步你的repository 的評價
- 關於fork教學 在 Git 與Github 版本控制基本指令與操作入門教學 - TechBridge ... 的評價
fork教學 在 Taipei Ethereum Meetup Facebook 的最佳解答
📜 [專欄新文章] 來聊聊MEV之亂
✍️ Anton Cheng
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
MEVA vs Fair Ordering.
Credit: MP頭條
前言
最近這幾個月來,以太坊上面的MEV(Miner Extractable Value)的話題越來越紅,對於該如何解決這之中的不公平性,社群中展開了很大的爭論。由於最近剛好看到一篇很棒的Tweet統整了這半年來的一些好文章,就試這整理一下這幾篇的主要論點。(如果對MEV已經有基本概念的人,也可以直接follow這個thread就好xD)
— @benjaminsimon97
由於主要辯論的雙方剛好是目前做Optimistic Rollup最有名的兩大團隊:Optimism 和 Arbitrum,因此我們也可以透過這次辯論看出兩個團隊未來的開發走向。
前情提要: 什麼是MEV (Miner Extractable Value)
在Ethereum現有設計中,Miner有著選擇交易收入區塊(Tx Inclusion),和決定交易先後順序(Tx Ordering)的權力。MEV指的就是礦工透過掌握這兩個權力所能提取的總價值。
雖然名詞定義上為礦工的可抽取價值,但是除了礦工之外,很多Front running 機器人在做的事情也是一樣的:透過觀察mempool裡面的交易,當發現front run機會的時候,透過付錢更高手續來讓自己的交易先被執行,這之中所抽取的價值,也是所謂的MEV,因此在後面會提到的很多文章中,大家直接用Front Run這個詞來代表了擷取MEV的行為。@danrobinson 在他經典的文章 Ethereum is a Dark Forest 中很好的闡述了如此的現況,也正式把這個問題帶進更多人的視野。
在那之後,一個名為FlashBots的組織現身,開始進行公開的MEV 相關研究與開發,旨在改善EVM帶來的負面影響,例如:front running 造成手續費提高、 MEV太高對以太坊安全性的影響。非常推薦大家Follow他們的進展。
接下來我們就來看看,一個重要的Proposal已經圍繞它而生的一些辯論。
MEVA (MEV Auction)
MEV Auction 是由 Karl Floersch 、Vitalik 、Philip Daian等人,於2020年一月共同提出的解決方法,字面上的翻譯就是透過一個拍賣(Auction)機制,讓MEV可以被更公平的分配。Karl 同時也是Optimism的CTO,也可能是因此設計出了這個能夠完美契合Layer2的架構。
更精準一點來說,這個拍賣要拍賣的是礦工的兩個權力中的「排序權」:它礦工未來只負責「選擇交易進入區塊」,而不再控制區塊內交易順序的排列,而把此權交給另外一個叫做Sequencer的角色來進行。至於Sequencer的選擇方式,就會是一個簡單的拍賣競標。在競標中獲勝的Sequencer將可以拿到未來一段時間的區塊排序權。至於整個協議競標中得來的錢,可以作為提供公共財的資金(fund public goods)。
為什麼說這個提案契合Layer2,是因為在L2原本的設計中,就是由一個Sequencer收取所有用戶的L2交易,在L2的鏈上執行,最後把執行結果以及所有交易資料Publish到L1上。換言之,這個排序者角色其實已經存在L2的架構中。若是L2層先實做看看這個拍賣機制,就可以在不改變以太核心協議的情況下,測試一下其可行性、參數等等。
對MEVA的質疑
在MEVA概念被提出之後,Ed Felton (Arbitrum 背後公司 Official Lab 的創辦人兼普林斯頓的教授),對此提出了諸多的質疑。
1. 用戶最終體驗是否變差
Ed Felton最先寫了一篇名為「MEV auctions considered harmful」的文章,其中質疑了這個設計會讓使用者體驗更糟:這個Ordering權力的競標,無異於將「Front Running」這個行為專業化,因為理論上最會front run的人,將能夠一直出最高價得標。這在使得「Tx Ordering」這個權力中心化的同時,更變向鼓勵了大家開發厲害的front running程式,最終的受害者仍是的以太坊一般用戶,因為所有的MEV其實都是從用戶的身上抽出來的。
MEV auctions considered harmful
Vitalik 對Ed的看法提出了反駁:他認為「MEV來自用戶」這件事是一個已知且不可避免的事實,這個機制主要的重點,在於分離MEV的收入與礦工的收入,藉由把這個金流轉給Sequencer這個非礦工的角色,可以去除礦工中心化等危及Layer1 安全的疑慮。一個簡單的例子就是:若是有一個MEV很高的區塊(假設礦工可以透過re-ordering拿到100個ETH),那麼礦工就有動機在這個區塊高度進行fork(希望最終自己的挖到區塊被網路接受)。這個例子讓我們看到,考慮MEV會使得礦工的行為比起「單純領block reward」更難預測,這將危及到Layer1的安全。
Vitalik也表示,專業化帶來的「中心化tx ordering」並不見得是件壞事,儘管它對於使用者體驗是有害的,但無論如何MEV是有個上限的,Sequencer並沒有權力從使用者口袋偷錢,而且用戶可以在任何時候決定不使用這種比較容易被Front-run的合約。
2. 沒有MEV Auction的話,L1真的會變得更中心化嗎
Ed 接著寫了一篇名為「Front Running as a service」的文章,簡單回應了中心化tx ordering的問題,也挑戰了另一個MEV Auction的假設:「MEV將使得L1 Mining power趨於中心化」。
這個假設背後的理由很簡單,假如有一個礦池特別會front run,它將能夠獲得比其他礦池更高的收入,這會吸引所有礦工轉到這個礦池。
Ed 提出的反駁理論也十分有趣:假設現在有兩個礦工:A與B,其中兩者都有一定的算力,但A有較好的MEV程式,因此能夠透過排列交易獲得更高的收益。在任何時間點,只要B還存在,A礦工就有動機把這個MEV程式「賣給」B,因為本來B也有機會挖到一些區塊,在這些區塊中A的收益為0。若是能夠達成一個互惠的條件:B將使用A的程式多賺的收入分一部分給A,那麼這筆交易對於礦工A與B而言都是有益的,因此這筆交易必定會發生。
Front-Running as a Service
其衍伸意義為:身為MEV專家的礦工,其實有動機提供「Front Running as a service」,所以最終這個Service會自然被分離出來,並且形成一個自己的市場,本質上跟MEVA是類似的,並不會導致L1算力中心化。
3. MEV Auction是否真的能分離Tx Inclusion 與 Tx Ordering
Ed 還寫了另外一篇「MEVA(What is it good for?)」的文章,用經濟學解釋為什麼這個Auction最終會失效。
MEVA (What is it good for?)
簡而言之,不管我們如何想要分離這兩個權力,最厲害的front-runner若是能夠同時掌握Tx Inclusion的權力,它必定能夠提高自己的收入。這也表示,最厲害的Sequencer會想要自己成立一個礦池,因為當他同時掌握Tx Inclusion和Tx Ordering 兩個權力時,他能夠提供最高的報酬。因此一個理性的Sequencer會願意透過提高給礦工們更高的獎勵,來壟斷Tx Inclusion + Tx Ordering的權力(這是一個在現實商業世界中非常常見的壟斷策略)。這會使得最後這個模式會變得跟現在一模一樣:由單一角色決定Tx Inclusion和Tx Ordering的權力。
Fair Sequencing
不難看出,Ed所有的論點都圍繞一個重點:MEV Auction最終並沒有辦法解決任何問題,而且這個拍賣還會為社群帶來更多問題:例如一次拍賣24小時的交易排序權力,會讓這個權力過度中心化。
那麼Ed所在的Official Lab有提出什麼解法嗎?其實有:他們認為真正解決這個問題的方法並不是在鼓勵專業化Front Run並且拍賣這個權力,而是從根本上消除Front Run的機會,也就是說,應該要設計一個機制「避免」任何人任意排列交易順序。也就是所謂的Fair Sequencing問題。
在Arbitrum目前的計畫中,在未來他們會在Arbitrum Layer2中引入一個這樣的「公平排序」。實際的細節還沒有太明朗,他們計劃在幾個月內上線的第一個Rollup 版本也不會包含這些功能,所以其實他們的Mainnet Launch會類似Optimism,由單一Sequencer決定所有交易排序。但還是很期待他們未來能不能夠真的實作出更好的方法。
Chainlink Labs: Fair Sequencing Service
另一位Chainlink Labs 研究員(身兼康乃爾的教授) Ari Juels,也因為最近在CoinDesk發表了一篇類似的文章質疑MEVA,讓這個問題加溫不少。在這篇文章中,用了一個非常極端的譬喻:把Front Run這種惡意行為比喻為犯罪,若是一個城市充滿了罪犯,一個政府該做的事情並不是拍賣大家入室盜竊的權力、再將這些拍賣所得拿來回饋人民。反之,政府應該想辦法阻止犯罪。
這篇文章透過這樣的比喻,來表達對於MEVA機制的不認同,同時譴責Flashbots這類的社群專案是在系統化的傷害使用者,並指出「如何解決MEV應該成為以太社群的研究重點」。
Opinion: Miners, Front-Running-as-a-Service Is Theft - CoinDesk
文末的結論跟Ed Felton 相同,都是認為Fair Sequencing 才是此問題真正的解決之道。這其實是呼應自己Chainlink Labs幾個月前發佈的一個 Fair Sequencing Service。簡而言之,就是透過Chainlink 最擅長的預言機(Oracle network)來投票、避免讓一個中心化Sequencer角色單獨掌握這個權力。
其實這個Fair Sequencing 問題是一個非常大的研究領域、除了Chainlink labs提出的Oracle解決方法以外,還有許多包括ZKP的其他解決方法。我會盡量多Follow一些,以後有機會再來做更多介紹。
社群中其他的聲音
對於這個MEVA vs Fair Ordering的爭論,除了理論派以外,也有不少其他的聲音讓我們可以更全面地看看的整個局勢。其中一則Tweet表示:MEV Auction在實作上比Fair Ordering單純許多、也不需要牽扯一些複雜的密碼學:
— @tarunchitra
最後一定要提一下的是另一個以太坊大佬 Hudson 對於Ari這篇文章的回應。他認為MEV問題在短期內將會持續困擾使用者,Flashbots這種公開透明的開發流程能夠吸引更多社群關注,並且能夠讓MEV更公平的分配給更多角色、而非只是少部分的玩家。同時他也指出,以太坊核心開發者目前有更重要的2.0開發工作要做,面對這個議題,大家應該更踴躍參與以太坊公開的研究流程,而不是把所有的責任丟到所謂的「Core Devs」身上。
— @hudsonjameson
個人覺得Hudson這一段回應其實很值得大家更多思考,除了在學術上的辯論以外,真正實質上的社群參與也是很重要的。
小結
整個MEV議題到目前為止還是一個ongoing debate,在接下來幾天會不會愈演愈烈、會不會有人提出新的觀點,都是非常值得大家關注的問題。就像文章開頭低一篇Tweet所說的,這是非常高學術性良性辯論,我這裡只簡單的收錄了一部分,由於是順著Official Lab的脈絡撰寫、難免有些偏頗,希望大家可以到Flashbots的Github了解更多,會對於目前Optimism + Flashbot那一派目前所在做的事情有更多的了解。
如果還沒有follow Philip Daian這個人,非常建議大家Follow他。他除了是Flashbots目前最主要的推手之一,更有趣的是,他現在還在Cornell 念PHD,並且是Ari的學生。這場師生大戰讓整個辯論更加戲劇性,但卻不失其高質量的本質。對於接下來還會有什麼發展,讓我們一起期待吧。
— @phildaian
來聊聊MEV之亂 was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
fork教學 在 吃貨愛健身教練coco Facebook 的精選貼文
「按摩槍篇」
在醫院工作的我,常被學員問
《教練,請問有推薦的按摩槍或有需要購買按摩槍嗎》?
⬇️
Coco與母親的日常
均有規律運動習慣,平日裡因長期健身及跳舞的關係,容易產生肌肉疲憊感與痠痛的問題,母親年事已高身體常常身體容易痠痛,之前都是我協助媽媽按摩服務💆
有了這一支按摩槍後,就是一件非常方便又有效益的事 👉🏻 https://reurl.cc/ynAvn8
之後協助媽媽放鬆的工作就交給按摩槍了👏👏
對coco工作上也很有幫助,按摩槍能協助我的學生恢復課後緊繃肌肉的狀態,讓我在工作上更有效益。
💡按摩槍該如何使用、功能是什麼?該注意什麼呢?
1-適用於肌肉組織/大肌群以及筋膜層。
2-藉由高振動頻率來放鬆肌肉及淺層筋膜。
3-避開骨頭與關節交接處,頸椎及脊椎也不建議自己使用。
這次要推薦的這支 https://reurl.cc/ynAvn8
也是Coco非常喜歡的一支
「Spectrum 迷你口袋按摩槍」👏👏👏
✅8角型護手手把
「可以不用太出力就能輕鬆掌握、流汗也不擔心滑手」
✅ 13.9cm超迷你、比iphone還短
485g超輕盈零負擔設計風格,還可長時間隨身攜帶。
✅ 最低45分貝,在家及出外旅遊散步辦公,都能隨時使用也不怕尷尬,超安靜🤫
✅ 充電一次可提供最高10小時的長時享受
✅ 極簡時尚運動風,超美有5色可選
✅四種按摩頭,針對不同肌群按摩
💡「四種頭型」怎麼用?
💙1-Fork Head/U型叉頭-「適用脊椎兩側肌肉」
💙2-Flat Head/平頭-「用於大肌肉群放鬆」
💙3-Bullet Head/子彈頭-「用於肌痛點或小肌群放鬆」
💙4-Ball Head/圓球頭-「適用全身肌肉群」「男女老少皆宜」
✅早鳥最低可享優惠$1800/支的價格
搶早鳥:https://reurl.cc/ynAvn8
還附贈專用收納袋、教學影片⋯⋯!
這支帶出門完全不佔空間,超方便的!!這麼棒的工具!!我隨身攜帶💗
擁有一支按摩槍真的不求人了!!
《最後還是要提醒各位同學》
按摩槍的用途主要還是放鬆和舒緩
並不能治療疾病,若是有急性運動傷害、扭傷、拉傷時,正處於急性發炎狀態還是要找專業醫師協助唷。🩺
fork教學 在 糖餃子Sweet Dumpling Youtube 的最佳貼文
Hello friends! Today we're going to share with you how to make a French style chocolate and vanilla cake: Chocolate Marble Cake (Marbré Au Chocolat).
Marbré Au Chocolat, aka Chocolate Marble Cake is an iconic French store-bought dessert. It’s traditionally baked in a loaf tin in France. This cake is very moist and not sweet, it’s a perfect treat for any time of the day.
This is a simple recipe and very easy to make, it tastes like pound cake, but the taste is richer and buttery, butter gives lots of flavor. And one serving of cake can taste the flavors of vanilla and cocoa. Heartily recommend. 💕
📍 Please follow me on Instagram: https://www.instagram.com/sweetdumplingofficial
📍 Welcome to follow me on FB: https://www.facebook.com/sweet.dumpling.studio
This is an #ASMR ver, you can check out the other version that with BGM and Voices in Chinese if you like:
https://youtu.be/e_YXyxxSxkg
----------
How to make French Chocolate Marble Cake
Here is the French Chocolate Marble Cake Recipe
☞ Mold size: 16x7x6cm
✎ Ingredients
unsalted butter 100g
powdered sugar 100g
egg 2, about 110g
vanilla extract 1.3g
honey 9g
All purpose flour 110g
baking powder 1.3g
cocoa powder 9g
a pinch of salt
✎ Instructions
1. Grease the cake pan with butter, then line with parchment paper.
2. Use a mesh strainer to sift powdered sugar. Then sift flour and baking powder together. Set aside.
3. Place softened butter and a pinch of salt into a large mixing bowl. Using a hand mixer on medium speed to cream the butter for 4-5 minutes, or until butter is pale yellow and fluffy. Use a rubber spatula to scrape the sides of the bowl once or twice while mixing.
4. Add half sugar and mix for 1 minute, then add the rest of sugar and vanilla extract, then mix for another 1 minute.
5. Add honey, and add egg one by one, and mix well after each egg is added. Scraping down the sides of the bowl as necessary.
6. Add the half sifted flour and baking powder, fold it in gently with spatula until the flour has disappeared. Repeat again with the remaining flour until well combined.
7. Simply pour 2/3 of the cake batter into a piping bag. Set aside.
8. Add the cacao powder into the rest of 1/3 of the batter and slowly mix it in until combined. Then pour the cacao batter into another piping bag.
9. Once the two cake batters are ready, it's time to combine them to create the marble effect. Cut off the tip and pipe the batter into the cake pan, alternating between the two colors.
10. After placing all the batters, you can use a fork, a bamboo stick or a butter knife to swirl the batters together. It's the fun part of the recipe, you can create your own marble pattern. Now the cake is marbled and ready to bake.
11. Preheat the oven to 200C, and bake for 15 minutes, then reduce the temperature to 160C, bake for 22~24 minutes.
12. Once baked, remove the cake from the pan, and let cool on the rack.
-----------------------------------------------------------------------
Chapter:
00:00 opening
00:32 Ingredients
01:24 preparation: butter pan, sieves dry ingredients
03:53 Make Marble Cake batter
07:42 Make cacao batter
09:08 fill the batter and make marble pattern
11:37 baking
12:27 taste with cream
-----------------------------------------------------------------------
#MarbleCake
#chocolateCake
#easyrecipes

fork教學 在 賴阿奇 Youtube 的最佳解答
注意!!台服不能用!!中文化不能用!!
因為POE OVERLAY FORK版沒辦法用了,所以我才嘗試使用這個新的查價軟體,非常不錯
下載連結:https://github.com/SnosMe/awakened-poe-trade/releases
歐付寶連結:
https://payment.opay.tw/Broadcaster/Donate/813F2FDEAB86CCC7CD7F15EAB2815A7B
我的實況連結stream link:
https://www.twitch.tv/freetitude
實況精華連結HighLight:
https://www.twitch.tv/freetitude/videos?filter=highlights&sort=time
我的人物檔案profile:
國際服GGG SERVER→https://www.pathofexile.com/account/view-profile/freetitude/characters
想看POB的話直接在POB的import上面打我的帳號freetitude就好

fork教學 在 賴阿奇 Youtube 的最讚貼文
經過我實際下載重新匯入確認這個POB FORK版的網址是對的,只是因為他現在都線上更新所以最新發布日期才會是24天前。
https://github.com/PathOfBuildingCommunity/PathOfBuilding/releases
歐付寶連結:
https://payment.opay.tw/Broadcaster/Donate/813F2FDEAB86CCC7CD7F15EAB2815A7B
我的實況連結stream link:
https://www.twitch.tv/freetitude
實況精華連結HighLight:
https://www.twitch.tv/freetitude/videos?filter=highlights&sort=time
我的人物檔案profile:
國際服GGG SERVER→https://www.pathofexile.com/account/view-profile/freetitude/characters
想看POB的話直接在POB的import上面打我的帳號freetitude就好

fork教學 在 30 天精通Git 版本控管(28):了解GitHub 的fork 與pull request ... 的推薦與評價
因此,GitHub 採用了forks 與pull request 的流程,讓你可以做到基本的權限控管。 設定GitHub 專案的權限控管- 個人帳號. 在GitHub 的個人帳戶下,並沒有甚麼權限控管 ... ... <看更多>
fork教學 在 6.2 GitHub - 參與一個專案 - Git SCM 的推薦與評價
所有人可以fork 專案,對fork 出來的專案推送變更,然後去發出我們等下會提到的Pull Request,來把這些變更貢獻回原本的專案裡。 這會開立一個能夠作程式碼審閱的討論串, ... ... <看更多>
fork教學 在 Re: [系程] 教學: 簡介fork, exec*, pipe, dup2 - 看板b97902HW 的推薦與評價
簡介 fork, exec*, dup2, pipe
實作 Command Interpreter 的 Pipeline:上一篇的綜合練習
看完上一篇,大家應該有能力寫一個具有 Pipeline 功能的簡單
Command Interpreter。所謂的 Command Interpreter 就像是
bash、ksh、tcsh 之類的東西,我們也稱之為 shell。一般而言
會是你登入一個系統之後第一個執行的程式。
而我們所談論的 Pipeline 有一點像 IO redirection。例如我
下達以下的指令:
command1 | command2 | command3
此時 command1 的 stdout 會被當作 command2 的 stdin;command2
的 stdout 會被當作 command3 的 stdin。而當上面的指令執行時,
command1 與 command3 的標準輸出都不會顯示到螢幕上。
例如:cat /etc/passwd 指令是用來把 /etc/passwd 這一個
檔案的檔案內容印到 stdout 上面;而 grep username 是從
stdin 讀入每一行,如果某一行有 username 就輸出該行到
標準輸出。所以當他們用 pipeline 組合在一起:
cat /etc/passwd | grep username
就會變成在螢幕上顯示 /etc/passwd 之中含有 username 的
那幾行。當然,如果靈活使用 pipeline 可以用很少的指令
變化出很多功能。因此 pipeline 在 *nix 環境下是很重要的
東西。你能用 open/close/dup2/exec*/fork 寫出一個具有
Pipeline 功能的 Command Interpreter 嗎?
以下是我寫到一半到程式碼,他已經可以把使用者輸入的指令
轉換成若干個可以傳給 execvp 的 argv,只剩 pipeline 的
部分還沒有寫完,你可以試著寫寫看:
https://w.csie.org/~b97073/B/todo-pipeline-shell.c
(防雷,按 Page Down 繼續閱讀)
你也可以直接下載我隨手寫的版本:
https://w.csie.org/~b97073/B/simple-pipeline-shell.c
這一份程式碼其實沒有新得東西,就是利用先前介紹過的:IO
redirection (red.c 使用的方法),與使用 fork/exec 來建立
child process。
我在執行 command1 的時候,我把他的 stdout 導向一個檔案。
當他結束之後,我再把這個檔案做為 stdin 導入 command2,
而 command2 的 stdout 再導入另一個檔案... 以下類推。
我們還是看一下其中的 creat_proc 與 execute_cmd_seq 二個函式:
/* Purpose: Create child process and redirect io. */
void creat_proc(char **argv, int fd_in, int fd_out)
{
/* creat_prc 函式主要的目的是建立 child process,並且做好 IO redirection。
它的參數有三個:argv 是將來要傳給 execvp 用的;fd_in、fd_out 分別是
輸入輸出的 file descriptor。 */
pid_t proc = fork();
if (proc < 0)
{
fprintf(stderr, "Error: Unable to fork.\n");
exit(EXIT_FAILURE);
}
else if (proc == 0)
{
if (fd_in != STDIN_FILENO)
{
/* 把 fd_in 複製到 STDIN_FILENO */
dup2(fd_in, STDIN_FILENO);
/* 因為 fd_in 沒有用了,就關掉他 */
close(fd_in);
}
if (fd_out != STDOUT_FILENO)
{
/* 把 fd_out 複製到 STDOUT_FILENO */
dup2(fd_out, STDOUT_FILENO);
/* 因為 fd_out 沒有用了,就關掉他 */
close(fd_out);
}
/* 載入可執行檔,我直接把 argv[0] 當成 executable name */
if (execvp(argv[0], argv) == -1)
{
fprintf(stderr,
"Error: Unable to load the executable %s.\n",
argv[0]);
exit(EXIT_FAILURE);
}
/* NEVER REACH */
exit(EXIT_FAILURE);
}
else
{
int status;
wait(&status); /* 等程式執行完畢 */
}
}
/* Purpose: Create several child process and redirect the standard output
* to the standard input of the later process.
*/
void execute_cmd_seq(char ***argvs)
{
int C;
for (C = 0; C <= MAX_CMD_COUNT; ++C)
{
char **argv = argvs[C];
if (!argv) { break; }
int fd_in = STDIN_FILENO;
int fd_out = STDOUT_FILENO;
if (C > 0)
{
/* 開啟暫存檔案 */
fd_in = open(pipeline_tmp_[C - 1], O_RDONLY);
if (fd_in == -1)
{
fprintf(stderr, "Error: Unable to open pipeline tmp r.\n");
exit(EXIT_FAILURE);
}
}
if (C < MAX_CMD_COUNT && argvs[C + 1] != NULL)
{
/* 開啟暫存檔案 */
fd_out = open(pipeline_tmp_[C],
O_WRONLY | O_CREAT | O_TRUNC,
0644);
if (fd_out == -1)
{
fprintf(stderr, "Error: Unable to open pipeline tmp w.\n");
exit(EXIT_FAILURE);
}
}
creat_proc(argv, fd_in, fd_out);
if (fd_in != STDIN_FILENO) { close(fd_in); }
if (fd_out != STDOUT_FILENO) { close(fd_out); }
}
}
直接用暫存檔案實作 pipeline 的缺點
不過上面直接用暫存檔案來達成 pipeline 有什麼缺點呢?
(1) 就是慢!因為不過是要讓二個程式相互溝通而已,實在沒有必要
把內容寫入硬碟。而且可能會用去為數不少的空間。例如:執行
這個指令一定很花時間與硬碟空間:
tar c / | tar xv -C .
(2) command1, command2, .. commandN 只能夠依序輪流執行。因為
如果 command1 還沒寫完,而 command2 讀得比較快,則 command2
可能誤以為 command1 的輸出已經結束了。所以為了避免資料不完
整,我們只能在 command1 結束之後再執行 command2。然而這樣可
能比較浪費時間。
那有沒有解決的方法呢?這就是我們下一個要介紹的系統呼叫:pipe()。
pipe:二個 Process 之間溝通的橋樑
pipe 顧名思意就是水管的意思,當我們呼叫 pipe 的時候,他會為
我們開啟二個 File descriptor,一個讓我們寫入資料,另一個讓我
們讀出資料。他的主要用途是讓二個 Process 可以互相溝通(Inter-
process Communication, IPC)。在大多數的系統中,pipe 是使用記
憶體來當 buffer,所以會比直接把檔案寫到硬碟有效率。pipe 的函
式原型如下:
int pipe(int fds[2]);
當我們呼叫 pipe 的時候,我們必需傳入一個大小至少為 2 的 int
陣列,pipe 會在 fds[0] 回傳一個 Read Only 的 File descriptor,
在 fds[1] 回傳一個 Write Only 的 File descriptor。當二個
Processs 要相互溝通的時候,就直接使用 write 系統呼叫把資料
寫進 pipe,而接收端就可以用 read 來讀取資料。
另外,和一般的檔案不同,除非 pipe 的 write-end (寫入端) 全部
都被 close 了,不然 read 會一直等待新的輸入,而不是以為已經
走到 eof。
備註:雖然我們是從 Pipeline 開始提到 pipe(),不過,Pipeline
未必要用 pipe() 實作。pipe() 的應用領域也不限於 Pipeline。
不過以 pipe() 實作 Pipeline 確實是一個很有效率的方法,
究我所知,GNU bash 就是使用 pipe() 來實作 Pipeline。
我們可以看一下一個簡單的 Multiprocess Random Generator 的範例:
/* 程式碼: pipe-example.c */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
enum { RANDOM_NUMBER_NEED_COUNT = 10 };
int main()
{
int pipe_fd[2];
if (pipe(pipe_fd) == -1) /* 建立 pipe */
{
fprintf(stderr, "Error: Unable to create pipe.\n");
exit(EXIT_FAILURE);
}
pid_t pid;
if ((pid = fork()) < 0) /* 注意:fork 的時候,pipe 的 fd 會被 dup */
{
fprintf(stderr, "Error: Unable to fork process.\n");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
/* -- In the Child Process -------- */
/* Close Read End */
close(pipe_fd[0]); /* close read end, since we don't need it. */
/* 我們在 Child Process 只想要當寫出端,所以我們就要先把 pipe 的 read
end 關掉 */
/* My Random Number Generator */
srand(time(NULL));
int i;
for (i = 0; i < RANDOM_NUMBER_NEED_COUNT; ++i)
{
sleep(1); // wait 1 second
int randnum = rand() % 100;
/* 把資料寫出去 */
write(pipe_fd[1], &randnum, sizeof(int));
}
exit(EXIT_SUCCESS);
}
else
{
/* -- In the Parent Process -------- */
/* Close Write End */
close(pipe_fd[1]); /* Close write end, since we don't need it. */
/* 不會用到 Write-end 的 Process 一定要把 Write-end 關掉,不然 pipe
的 Read-end 會永遠等不到 EOF。 */
int i;
for (i = 0; i < RANDOM_NUMBER_NEED_COUNT; ++i)
{
int gotnum;
/* 從 Read-end 把資料拿出來 */
read(pipe_fd[0], &gotnum, sizeof(int));
printf("got number : %d\n", gotnum);
}
}
return EXIT_SUCCESS;
}
雖然上面的例子展示了二個 Process 之間如何溝通。不過只看這個
例子看不出 pipe 的價值。我們的第二個例子就是要利用 pipe 來
攔截另一個 Program 的 standard output。
在第二個例子之中,我們會有二個 Program,也就是會有二個可執行
檔案。其中一個專門付負製造 Random Number,然後直接把 32-bit
int 寫到 standard output。而令一個會去呼叫前述的 Random Number
製造程式,然後攔截他的 standard output。
/* 程式碼: random-gen.c */
/* 這一個檔案就沒有什麼特別的,就只是不斷製造 Random Number */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
enum { RANDOM_NUMBER_NEED_COUNT = 10 };
int main()
{
srand(time(NULL));
int i;
for (i = 0; i < RANDOM_NUMBER_NEED_COUNT; ++i)
{
sleep(1); /* Wait 1 second. Simulate the complex process of
generating the safer random number. */
int randnum = rand() % 100;
write(STDOUT_FILENO, &randnum, sizeof(int));
/* 注意:是寫到 stdout 。*/
}
return EXIT_SUCCESS;
}
/* 程式碼:pipe-example-2.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
enum { RANDOM_NUMBER_NEED_COUNT = 10 };
int main()
{
/* -- Prepare Pipe -------- */
int pipe_fd[2];
if (pipe(pipe_fd) == -1)
{
fprintf(stderr, "Error: Unable to create pipe.\n");
exit(EXIT_FAILURE);
}
/* -- Create Child Process -------- */
pid_t pid;
if ((pid = fork()) < 0)
{
fprintf(stderr, "Error: Unable to create child process.\n");
exit(EXIT_FAILURE);
}
else if (pid == 0) /* In Child Process */
{
/* Close Read End */
close(pipe_fd[0]); /* Close read end, since we don't need it. */
/* Bind Write End to Standard Out */
dup2(pipe_fd[1], STDOUT_FILENO);
/* 把第 pipe_fd[1] 個 file descriptor 複製到第 STDOUT_FILENO 個
file descriptor */
/* Close pipe_fd[1] File Descriptor */
close(pipe_fd[1]);
/* 說明:經過上面三個步驟之後,這個 Child Process 的第 1 號 File
Descriptor 會是 pipe 的 Write-end,所以在我們做標準輸出的時候,
所有的資料都跑進我們的 pipe 裡面。因此另一端的 Read-end 就可以
接收到 random-gen 的標準輸出。 */
/* Load Another Executable */
execl("random-gen", "./random-gen", (char *)0);
/* This Process Should Never Go Here */
fprintf(stderr, "Error: Unexcept flow of control.\n");
exit(EXIT_FAILURE);
}
else /* In Parent Process */
{
/* Close pipe_fd[1] File Descriptor */
close(pipe_fd[1]); /* Close write end, since we will not use it. */
/* Read Random Number From Pipe */
int i;
for (i = 0; i < RANDOM_NUMBER_NEED_COUNT; ++i)
{
int gotnum = -1;
read(pipe_fd[0], &gotnum, sizeof(int));
printf("got number : %d\n", gotnum);
}
}
return EXIT_SUCCESS;
}
再回頭寫 Command Interpreter:加上 pipe() 系統呼叫,你可以寫得更好嗎?
這是我寫得另一個版本(使用 pipe() 的版本):
https://w.csie.org/~b97073/B/faster-pipeline-shell.c
這次我先檢查指令有多少個 '|',這代表我要準備多少的 pipe。接
著我為每一個 commandI 都用 fork 建立一個 Process,讓所有的
Process 可以用時執行。
另外,使用 pipe() 來實作有一個好處,就是如果 command2 要
read 東西,可是 command1 還沒有算完,command2 的 read 就會
一直等下去。所以我們不用依序輪流執行。所有的 process 可以
並行運作,除非遇到 IO blocking。而且使用 pipe() 也省去了暫
存檔案命名的困擾。
但是寫 pipe 的版本就要注意:對於所有的 Process,如果該 Process
不需要 Write-end 就一定要記得關掉他,不然像是 cat 或者 grep
的程式就會一直等不到 EOF,也就不會結束了!
我們可以快速地看一下 execute_cmd_seq 與 creat_proc 二個函式:
/* Purpose: Create several child process and redirect the standard output
* to the standard input of the later process.
*/
void execute_cmd_seq(char ***argvs)
{
int C, P;
int cmd_count = 0;
while (argvs[cmd_count]) { ++cmd_count; }
int pipeline_count = cmd_count - 1;
int pipes_fd[MAX_CMD_COUNT][2];
/* 準備足夠的 pipe */
for (P = 0; P < pipeline_count; ++P)
{
if (pipe(pipes_fd[P]) == -1)
{
fprintf(stderr, "Error: Unable to create pipe. (%d)\n", P);
exit(EXIT_FAILURE);
}
}
for (C = 0; C < cmd_count; ++C)
{
int fd_in = (C == 0) ? (STDIN_FILENO) : (pipes_fd[C - 1][0]);
int fd_out = (C == cmd_count - 1) ? (STDOUT_FILENO) : (pipes_fd[C][1]);
/* 呼叫下面的 creat_proc 來建立 Child Process */
creat_proc(argvs[C], fd_in, fd_out, pipeline_count, pipes_fd);
}
/* 在建立所有 Child Process 之後,Parent Process 本身就不必使用 pipe
了,所以關閉所有的 File descriptor。*/
for (P = 0; P < pipeline_count; ++P)
{
close(pipes_fd[P][0]);
close(pipes_fd[P][1]);
}
/* 等待所有的程式執行完畢 */
for (C = 0; C < cmd_count; ++C)
{
int status;
wait(&status);
}
}
/* Purpose: Create child process and redirect io. */
void creat_proc(char **argv,
int fd_in, int fd_out,
int pipes_count, int pipes_fd[][2])
{
pid_t proc = fork();
if (proc < 0)
{
fprintf(stderr, "Error: Unable to fork.\n");
exit(EXIT_FAILURE);
}
else if (proc == 0)
{
/* 把 fd_in 與 fd_out 分別當成 stdin 與 stdout。 */
if (fd_in != STDIN_FILENO) { dup2(fd_in, STDIN_FILENO); }
if (fd_out != STDOUT_FILENO) { dup2(fd_out, STDOUT_FILENO); }
/* 除了 stdin, stdout 之外,所有的 File descriptor (pipe) 都要關閉。*/
int P;
for (P = 0; P < pipes_count; ++P)
{
close(pipes_fd[P][0]);
close(pipes_fd[P][1]);
}
if (execvp(argv[0], argv) == -1)
{
fprintf(stderr,
"Error: Unable to load the executable %s.\n",
argv[0]);
exit(EXIT_FAILURE);
}
/* NEVER REACH */
exit(EXIT_FAILURE);
}
}
結語
我們從一個簡單的 io redirect 程式談起。一路介紹了 exec, fork,
dup2, pipe 等系統呼叫。還寫了一個簡單的 Command Interpreter。
希望可以透過這二篇小小的篇幅,讓大家能對上面四個系統呼叫更為
熟悉。
備註:這二篇大部分的程式碼可以在以下的網址取得:
https://w.csie.org/~b97073/B/sp-article2.tar.gz
(完)
--
LoganChien ----- from PTT2 個板 logan -----
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.247.159
※ 編輯: LoganChien 來自: 140.112.247.159 (03/19 07:10)
... <看更多>