rticle/details/79817039
前兩天做了一個導入的功能,導入開始的時候非常慢,導入2w條數據要1分多鐘,后來一點一點的優化,從直接把list懟進Mysql中,到分配把list導入Mysql中,到多線程把list導入Mysql中。時間是一點一點的變少了。非常的爽,最后變成了10s以內。下面就展示一下過程。
使用mybatis的批量導入操作:
@Transactional(rollbackFor = Exception.class)
public int addFreshStudentsNew2(List<FreshStudentAndStudentModel> list, String schoolNo) {
if (list == null || list.isEmpty()) {
return 0;
}
List<StudentEntity> studentEntityList = new LinkedList<>();
List<EnrollStudentEntity> enrollStudentEntityList = new LinkedList<>();
List<AllusersEntity> allusersEntityList = new LinkedList<>();
for (FreshStudentAndStudentModel freshStudentAndStudentModel : list) {
EnrollStudentEntity enrollStudentEntity = new EnrollStudentEntity();
StudentEntity studentEntity = new StudentEntity();
BeanUtils.copyProperties(freshStudentAndStudentModel, studentEntity);
BeanUtils.copyProperties(freshStudentAndStudentModel, enrollStudentEntity);
String operator = TenancyContext.UserID.get();
String studentId = BaseUuidUtils.base58Uuid();
enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
enrollStudentEntity.setStudentId(studentId);
enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
enrollStudentEntity.setOperator(operator);
studentEntity.setId(studentId);
studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
studentEntity.setOperator(operator);
studentEntityList.add(studentEntity);
enrollStudentEntityList.add(enrollStudentEntity);
AllusersEntity allusersEntity = new AllusersEntity();
allusersEntity.setId(enrollStudentEntity.getId());
allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
allusersEntity.setUserName(enrollStudentEntity.getName());
allusersEntity.setSchoolNo(schoolNo);
allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
allusersEntity.setPassword(enrollStudentEntity.getNemtCode()); //密碼設置為考生號
allusersEntityList.add(allusersEntity);
}
enResult = enrollStudentDao.insertAll(enrollStudentEntityList);
stuResult = studentDao.insertAll(studentEntityList);
allResult = allusersFacade.insertUserList(allusersEntityList);
if (enResult > 0 && stuResult > 0 && allResult) {
return 10;
}
return -10;
}
Mapper.xml
<insert id="insertAll" parameterType="com.dmsdbj.itoo.basicInfo.entity.EnrollStudentEntity">
insert into tb_enroll_student
<trim prefix="(" suffix=")" suffixOverrides=",">
id,
remark,
nEMT_aspiration,
nEMT_code,
nEMT_score,
student_id,
identity_card_id,
level,
major,
name,
nation,
secondary_college,
operator,
sex,
is_delete,
account_address,
native_place,
original_place,
used_name,
pictrue,
join_party_date,
political_status,
tel_num,
is_registry,
graduate_school,
create_time,
update_time </trim>
values
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.id,jdbcType=VARCHAR},
#{item.remark,jdbcType=VARCHAR},
#{item.nemtAspiration,jdbcType=VARCHAR},
#{item.nemtCode,jdbcType=VARCHAR},
#{item.nemtScore,jdbcType=VARCHAR},
#{item.studentId,jdbcType=VARCHAR},
#{item.identityCardId,jdbcType=VARCHAR},
#{item.level,jdbcType=VARCHAR},
#{item.major,jdbcType=VARCHAR},
#{item.name,jdbcType=VARCHAR},
#{item.nation,jdbcType=VARCHAR},
#{item.secondaryCollege,jdbcType=VARCHAR},
#{item.operator,jdbcType=VARCHAR},
#{item.sex,jdbcType=VARCHAR},
0,
#{item.accountAddress,jdbcType=VARCHAR},
#{item.nativePlace,jdbcType=VARCHAR},
#{item.originalPlace,jdbcType=VARCHAR},
#{item.usedName,jdbcType=VARCHAR},
#{item.pictrue,jdbcType=VARCHAR},
#{item.joinPartyDate,jdbcType=VARCHAR},
#{item.politicalStatus,jdbcType=VARCHAR},
#{item.telNum,jdbcType=VARCHAR},
#{item.isRegistry,jdbcType=TINYINT},
#{item.graduateSchool,jdbcType=VARCHAR},
now(),
now()
)
</foreach>
</insert>
代碼說明:
底層的mapper是通過逆向工程來生成的,批量插入如下,是拼接成類似:insert into tb_enroll_student()values (),()…….();
這樣的缺點是,數據庫一般有一個默認的設置,就是每次sql操作的數據不能超過4M。這樣插入,數據多的時候,數據庫會報錯Packet for query is too large (6071393 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.,雖然我們可以通過
類似 修改 my.ini 加上 max_allowed_packet =67108864,67108864=64M,默認大小4194304 也就是4M
修改完成之后要重啟mysql服務,如果通過命令行修改就不用重啟mysql服務。
完成本次操作,但是我們不能保證項目單次最大的大小是多少,這樣是有弊端的。所以可以考慮進行分組導入。
同樣適用mybatis批量插入,區別是對每次的導入進行分組計算,然后分多次進行導入:
@Transactional(rollbackFor = Exception.class)
public int addFreshStudentsNew2(List<FreshStudentAndStudentModel> list, String schoolNo) {
if (list == null || list.isEmpty()) {
return 0;
}
List<StudentEntity> studentEntityList = new LinkedList<>();
List<EnrollStudentEntity> enrollStudentEntityList = new LinkedList<>();
List<AllusersEntity> allusersEntityList = new LinkedList<>();
for (FreshStudentAndStudentModel freshStudentAndStudentModel : list) {
EnrollStudentEntity enrollStudentEntity = new EnrollStudentEntity();
StudentEntity studentEntity = new StudentEntity();
BeanUtils.copyProperties(freshStudentAndStudentModel, studentEntity);
BeanUtils.copyProperties(freshStudentAndStudentModel, enrollStudentEntity);
String operator = TenancyContext.UserID.get();
String studentId = BaseUuidUtils.base58Uuid();
enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
enrollStudentEntity.setStudentId(studentId);
enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
enrollStudentEntity.setOperator(operator);
studentEntity.setId(studentId);
studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
studentEntity.setOperator(operator);
studentEntityList.add(studentEntity);
enrollStudentEntityList.add(enrollStudentEntity);
AllusersEntity allusersEntity = new AllusersEntity();
allusersEntity.setId(enrollStudentEntity.getId());
allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
allusersEntity.setUserName(enrollStudentEntity.getName());
allusersEntity.setSchoolNo(schoolNo);
allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
allusersEntity.setPassword(enrollStudentEntity.getNemtCode()); //密碼設置為考生號
allusersEntityList.add(allusersEntity);
}
int c = 100;
int b = enrollStudentEntityList.size() / c;
int d = enrollStudentEntityList.size() % c;
int enResult = 0;
int stuResult = 0;
boolean allResult = false;
for (int e = c; e <= c * b; e = e + c) {
enResult = enrollStudentDao.insertAll(enrollStudentEntityList.subList(e - c, e));
stuResult = studentDao.insertAll(studentEntityList.subList(e - c, e));
allResult = allusersFacade.insertUserList(allusersEntityList.subList(e - c, e));
}
if (d != 0) {
enResult = enrollStudentDao.insertAll(enrollStudentEntityList.subList(c * b, enrollStudentEntityList.size()));
stuResult = studentDao.insertAll(studentEntityList.subList(c * b, studentEntityList.size()));
allResult = allusersFacade.insertUserList(allusersEntityList.subList(c * b, allusersEntityList.size()));
}
if (enResult > 0 && stuResult > 0 && allResult) {
return 10;
}
return -10;
}
代碼說明:
這樣操作,可以避免上面的錯誤,但是分多次插入,無形中就增加了操作實踐,很容易超時。所以這種方法還是不值得提倡的。
再次改進,使用多線程分批導入。
依然使用mybatis的批量導入,不同的是,根據線程數目進行分組,然后再建立多線程池,進行導入。
@Transactional(rollbackFor = Exception.class)
public int addFreshStudentsNew(List<FreshStudentAndStudentModel> list, String schoolNo) {
if (list == null || list.isEmpty()) {
return 0;
}
List<StudentEntity> studentEntityList = new LinkedList<>();
List<EnrollStudentEntity> enrollStudentEntityList = new LinkedList<>();
List<AllusersEntity> allusersEntityList = new LinkedList<>();
list.forEach(freshStudentAndStudentModel -> {
EnrollStudentEntity enrollStudentEntity = new EnrollStudentEntity();
StudentEntity studentEntity = new StudentEntity();
BeanUtils.copyProperties(freshStudentAndStudentModel, studentEntity);
BeanUtils.copyProperties(freshStudentAndStudentModel, enrollStudentEntity);
String operator = TenancyContext.UserID.get();
String studentId = BaseUuidUtils.base58Uuid();
enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
enrollStudentEntity.setStudentId(studentId);
enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
enrollStudentEntity.setOperator(operator);
studentEntity.setId(studentId);
studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
studentEntity.setOperator(operator);
studentEntityList.add(studentEntity);
enrollStudentEntityList.add(enrollStudentEntity);
AllusersEntity allusersEntity = new AllusersEntity();
allusersEntity.setId(enrollStudentEntity.getId());
allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
allusersEntity.setUserName(enrollStudentEntity.getName());
allusersEntity.setSchoolNo(schoolNo);
allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
allusersEntity.setPassword(enrollStudentEntity.getNemtCode()); //密碼設置為考生號
allusersEntityList.add(allusersEntity);
});
int nThreads = 50;
int size = enrollStudentEntityList.size();
ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
List<Future<Integer>> futures = new ArrayList<Future<Integer>>(nThreads);
for (int i = 0; i < nThreads; i++) {
final List<EnrollStudentEntity> EnrollStudentEntityImputList = enrollStudentEntityList.subList(size / nThreads * i, size / nThreads * (i + 1));
final List<StudentEntity> studentEntityImportList = studentEntityList.subList(size / nThreads * i, size / nThreads * (i + 1));
final List<AllusersEntity> allusersEntityImportList = allusersEntityList.subList(size / nThreads * i, size / nThreads * (i + 1));
Callable<Integer> task1 = () -> {
studentSave.saveStudent(EnrollStudentEntityImputList,studentEntityImportList,allusersEntityImportList);
return 1;
};
futures.add(executorService.submit(task1));
}
executorService.shutdown();
if (!futures.isEmpty() && futures != null) {
return 10;
}
return -10;
}
代碼說明:
上面是通過應用ExecutorService 建立了固定的線程數,然后根據線程數目進行分組,批量依次導入。一方面可以緩解數據庫的壓力,另一個面線程數目多了,一定程度會提高程序運行的時間。缺點就是要看服務器的配置,如果配置好的話就可以開多點線程,配置差的話就開小點。
隨著三代銳龍處理器的發布,關于新AU的期待與討論也越來越多。不過新銳龍“新”的地方太多了,從制程到架構,玩家們對此感到不明覺厲的同時也眼花繚亂,反而搞不太清對于一般用戶來說,銳龍3000系列到底比上代/競品強了多少,強在了哪些地方。
那么,今天就讓我們好好捋一捋頭緒,認真品品從體驗角度看,新銳龍的提升究竟在何處。
單線程和游戲性能:最高提升21%,R5默秒全
但凡提及游戲,單線程性能就是繞不過去的坎,AMD當然很懂玩家的心理,所以就把自家上代旗艦R7 2700X拿出來作為靶子“吊打”了一番。
AMD以目前最新的測試環境Cinebench R20作為性能測試基準,PPT顯示即便是R5 3600的單線程性能都比上代旗艦強出了9%,相同定位的R7 3800X則整整提升了19%,R9 3900X是21%。對比隔壁,這簡直是不可思議的提升幅度,但結合實際情況就會發現只是常規操作。
AMD官方PPT給出的結論是,這21%的性能提升,40%得益于7nm工藝和頻率提升,60%則得益于架構方面的優化。而在其他方面,Zen3相較Zen2的提升還有L3緩存翻倍、內存頻率從DDR4-2667提升至DDR4-3600、指令集更新、以及內存管理方面的顯著進步等。這些很復雜,就不詳細講了。
在這樣的提升下,3代銳龍自游戲方面“吊打”自家上代是毫無爭議的,即便是與Intel相同定位下的產品之間直接對比也是難分伯仲。1080P環境下,R9 3900X和i9-9900K的幀率表現完全處于同一水平線,AMD單核弱雞不能玩游戲的帽子終于能摘掉了。
也就是說,使用同等定位的AU和IU進行主流游戲時,將幾乎感受不到差異。
Windows深層優化:反應更快、更適合游戲
新銳龍的進步并不只有硬件層面,指令集的更新和微軟針對其的深層次優化也是同步安排上滴。
先說Windows方面的優化。事實上,對PC發展有一定了解的讀者都知道所謂“Wintle”聯盟的存在,事實上Windows針對Intel微架構布局的優化做得相當到位——換一個角度來說,對非Intel微架構就很不友好了。
不過這次微軟對AMD的支持就相當的深入且到位,具體集中在兩個方面:
其一是線程分配策略,從混合線程擴展轉向線程分組。簡而言之,就是在面對任務時,集中分配至少數CCX(核心復合體)上以保證高性能和高效率。需要注意的是,這并不是以前所謂的“一核有難,多核圍觀”,而是基于性能提升與架構組成制定的合力計算資源調配。AMD認為其雖然可能造成部分CCX閑置,但對于整體性能仍然有益。
這樣的策略轉變會讓三代銳龍處理器在線程數較少的任務場景下表現更優,例如游戲。毫無疑問這是一個更傾向于普通家用/游戲用戶的調整。
其二,則是頻率提升時間的大幅優化。我們都知道,更快的ramp-to-load頻率跳變能力能夠讓CPU應付突發驅動工作負載(burst-driven workload)時更占優勢,Intel通過“Speed Shift”技術使得Kaby Lake處理器的頻率提升速度下降至15毫秒,這一數字在Zen架構上是30毫秒。
AMD在Zen2上通過Collaborative Power Performance Control 2技術, 將Zen2架構的頻率提升時間降低至僅為1-2毫秒,遠勝于Intel。AMD稱,在PCMark10的應用程序啟動子測試中,啟動時間性能有著6%的提升。
落實到使用層面,用戶會發現打開程序的速度更快了。
這兩者都需要5月10日的Windows更新以及相應的BIOS更新,從日期上看AMD和微軟真是準備已久了。
PCIe 4.0:對專業用戶的吸引力更大
最后,就是廣為外界關注、卻沒有被AMD在技術日著力吹噓的PCIe 4.0了。我想這是由兩方面原因造成的,一是PCIe 4.0目前相配套的硬件環境還不成熟,二是在普通用戶層面的體驗并不能帶來質的飛躍。
不過,盡管目前PCIe 4.0 SSD的價格遠談不上親民,一般用戶也不會去配一塊來玩游戲,但對于專業領域,尤其是對多媒體創作者、AI開發者來說,超高速的SSD所能帶來的優勢是無法替代的。盡管AMD目前為止在第三代銳龍處理器上的調優策略都多多少少地傾向于家用/游戲用戶,但無論是核心數的飆升,還是對超高速SSD的支持,都會吸引到專業用戶的青睞。
總結
盡管AMD在新一代銳龍上的更新從技術角度來說很復雜、也有頗多值得稱道之處,但對于普通用戶來說,他們關心還始終還是好不好用。通過對目前AMD披露信息的分析,我們可以對三代銳龍處理器在日常使用中的表現有個大致的期待:系統更流暢、反應更靈敏、在游戲中的表現全面比肩Intel。
不過AMD高層在技術日中稱,他們并不希望將自身產品不斷地與Intel進行跨代比較,而是希望在每一次更新中都盡可能地壓榨出性能潛力來。這樣的態度無疑是值得稱道的,至少,這算是親口承認了“不擠牙膏”。此后的AMD或許將不只是性價比的代名詞,“性能”的標簽,或許會優于“價格”被人們所重新認識。