Java對象序列化的本機C / C ++類似性能

您是否曾經希望過像使用C ++這樣的本地語言將Java對象轉換成字節流一樣快的速度? 如果您使用標準的Java序列化,您可能會對性能感到失望。 Java序列化的目的是與盡可能快而緊湊地序列化對象的目的截然不同。

為什么我們需要快速緊湊的序列化? 我們的許多系統都是分布式的,我們需要通過在流程之間高效地傳遞狀態進行通信。 這種狀態存在于我們的物體內部。 我已經介紹了許多系統,通常大部分成本是該狀態與字節緩沖區之間的串行化。 我已經看到用于實現此目的的大量協議和機制。 一方面是易于使用但效率低下的協議,例如Java 序列化 , XML和JSON 。 另一方面,二進制協議可以非常快速和高效,但是需要更深入的理解和技能。

在本文中,我將說明使用簡單的二進制協議時可能實現的性能提升,并介紹一種Java中可用的鮮為人知的技術,以實現與C或C ++之類的本地語言類似的性能。

要比較的三種方法是:

  1. Java序列化 :Java中有一個對象實現Serializable的標準方法。
  2. 通過ByteBuffer進行二進制 :使用ByteBuffer API的簡單協議,以二進制格式寫入對象的字段。 這是我們認為是好的二進制編碼方法的基準。
  3. 二進制通過不安全 :介紹不安全和其允許直接內存操作方法的集合。 在這里,我將展示如何獲得與C / C ++類似的性能。

編碼

import sun.misc.Unsafe;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Arrays;public final class TestSerialisationPerf
{public static final int REPETITIONS = 1 * 1000 * 1000;private static ObjectToBeSerialised ITEM =new ObjectToBeSerialised(1010L, true, 777, 99,new double[]{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0},new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});public static void main(final String[] arg) throws Exception{for (final PerformanceTestCase testCase : testCases){for (int i = 0; i < 5; i++){testCase.performTest();System.out.format('%d %s\twrite=%,dns read=%,dns total=%,dns\n',i,testCase.getName(),testCase.getWriteTimeNanos(),testCase.getReadTimeNanos(),testCase.getWriteTimeNanos() + testCase.getReadTimeNanos());if (!ITEM.equals(testCase.getTestOutput())){throw new IllegalStateException('Objects do not match');}System.gc();Thread.sleep(3000);}}}private static final PerformanceTestCase[] testCases ={new PerformanceTestCase('Serialisation', REPETITIONS, ITEM){ByteArrayOutputStream baos = new ByteArrayOutputStream();public void testWrite(ObjectToBeSerialised item) throws Exception{for (int i = 0; i < REPETITIONS; i++){baos.reset();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(item);oos.close();}}public ObjectToBeSerialised testRead() throws Exception{ObjectToBeSerialised object = null;for (int i = 0; i < REPETITIONS; i++){ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);object = (ObjectToBeSerialised)ois.readObject();}return object;}},new PerformanceTestCase('ByteBuffer', REPETITIONS, ITEM){ByteBuffer byteBuffer = ByteBuffer.allocate(1024);public void testWrite(ObjectToBeSerialised item) throws Exception{for (int i = 0; i < REPETITIONS; i++){byteBuffer.clear();item.write(byteBuffer);}}public ObjectToBeSerialised testRead() throws Exception{ObjectToBeSerialised object = null;for (int i = 0; i < REPETITIONS; i++){byteBuffer.flip();object = ObjectToBeSerialised.read(byteBuffer);}return object;}},new PerformanceTestCase('UnsafeMemory', REPETITIONS, ITEM){UnsafeMemory buffer = new UnsafeMemory(new byte[1024]);public void testWrite(ObjectToBeSerialised item) throws Exception{for (int i = 0; i < REPETITIONS; i++){buffer.reset();item.write(buffer);}}public ObjectToBeSerialised testRead() throws Exception{ObjectToBeSerialised object = null;for (int i = 0; i < REPETITIONS; i++){buffer.reset();object = ObjectToBeSerialised.read(buffer);}return object;}},};
}abstract class PerformanceTestCase
{private final String name;private final int repetitions;private final ObjectToBeSerialised testInput;private ObjectToBeSerialised testOutput;private long writeTimeNanos;private long readTimeNanos;public PerformanceTestCase(final String name, final int repetitions,final ObjectToBeSerialised testInput){this.name = name;this.repetitions = repetitions;this.testInput = testInput;}public String getName(){return name;}public ObjectToBeSerialised getTestOutput(){return testOutput;}public long getWriteTimeNanos(){return writeTimeNanos;}public long getReadTimeNanos(){return readTimeNanos;}public void performTest() throws Exception{final long startWriteNanos = System.nanoTime();testWrite(testInput);writeTimeNanos = (System.nanoTime() - startWriteNanos) / repetitions;final long startReadNanos = System.nanoTime();testOutput = testRead();readTimeNanos = (System.nanoTime() - startReadNanos) / repetitions;}public abstract void testWrite(ObjectToBeSerialised item) throws Exception;public abstract ObjectToBeSerialised testRead() throws Exception;
}class ObjectToBeSerialised implements Serializable
{private static final long serialVersionUID = 10275539472837495L;private final long sourceId;private final boolean special;private final int orderCode;private final int priority;private final double[] prices;private final long[] quantities;public ObjectToBeSerialised(final long sourceId, final boolean special,final int orderCode, final int priority,final double[] prices, final long[] quantities){this.sourceId = sourceId;this.special = special;this.orderCode = orderCode;this.priority = priority;this.prices = prices;this.quantities = quantities;}public void write(final ByteBuffer byteBuffer){byteBuffer.putLong(sourceId);byteBuffer.put((byte)(special ? 1 : 0));byteBuffer.putInt(orderCode);byteBuffer.putInt(priority);byteBuffer.putInt(prices.length);for (final double price : prices){byteBuffer.putDouble(price);}byteBuffer.putInt(quantities.length);for (final long quantity : quantities){byteBuffer.putLong(quantity);}}public static ObjectToBeSerialised read(final ByteBuffer byteBuffer){final long sourceId = byteBuffer.getLong();final boolean special = 0 != byteBuffer.get();final int orderCode = byteBuffer.getInt();final int priority = byteBuffer.getInt();final int pricesSize = byteBuffer.getInt();final double[] prices = new double[pricesSize];for (int i = 0; i < pricesSize; i++){prices[i] = byteBuffer.getDouble();}final int quantitiesSize = byteBuffer.getInt();final long[] quantities = new long[quantitiesSize];for (int i = 0; i < quantitiesSize; i++){quantities[i] = byteBuffer.getLong();}return new ObjectToBeSerialised(sourceId, special, orderCode, priority, prices, quantities);}public void write(final UnsafeMemory buffer){buffer.putLong(sourceId);buffer.putBoolean(special);buffer.putInt(orderCode);buffer.putInt(priority);buffer.putDoubleArray(prices);buffer.putLongArray(quantities);}public static ObjectToBeSerialised read(final UnsafeMemory buffer){final long sourceId = buffer.getLong();final boolean special = buffer.getBoolean();final int orderCode = buffer.getInt();final int priority = buffer.getInt();final double[] prices = buffer.getDoubleArray();final long[] quantities = buffer.getLongArray();return new ObjectToBeSerialised(sourceId, special, orderCode, priority, prices, quantities);}@Overridepublic boolean equals(final Object o){if (this == o){return true;}if (o == null || getClass() != o.getClass()){return false;}final ObjectToBeSerialised that = (ObjectToBeSerialised)o;if (orderCode != that.orderCode){return false;}if (priority != that.priority){return false;}if (sourceId != that.sourceId){return false;}if (special != that.special){return false;}if (!Arrays.equals(prices, that.prices)){return false;}if (!Arrays.equals(quantities, that.quantities)){return false;}return true;}
}class UnsafeMemory
{private static final Unsafe unsafe;static{try{Field field = Unsafe.class.getDeclaredField('theUnsafe');field.setAccessible(true);unsafe = (Unsafe)field.get(null);}catch (Exception e){throw new RuntimeException(e);}}private static final long byteArrayOffset = unsafe.arrayBaseOffset(byte[].class);private static final long longArrayOffset = unsafe.arrayBaseOffset(long[].class);private static final long doubleArrayOffset = unsafe.arrayBaseOffset(double[].class);private static final int SIZE_OF_BOOLEAN = 1;private static final int SIZE_OF_INT = 4;private static final int SIZE_OF_LONG = 8;private int pos = 0;private final byte[] buffer;public UnsafeMemory(final byte[] buffer){if (null == buffer){throw new NullPointerException('buffer cannot be null');}this.buffer = buffer;}public void reset(){this.pos = 0;}public void putBoolean(final boolean value){unsafe.putBoolean(buffer, byteArrayOffset + pos, value);pos += SIZE_OF_BOOLEAN;}public boolean getBoolean(){boolean value = unsafe.getBoolean(buffer, byteArrayOffset + pos);pos += SIZE_OF_BOOLEAN;return value;}public void putInt(final int value){unsafe.putInt(buffer, byteArrayOffset + pos, value);pos += SIZE_OF_INT;}public int getInt(){int value = unsafe.getInt(buffer, byteArrayOffset + pos);pos += SIZE_OF_INT;return value;}public void putLong(final long value){unsafe.putLong(buffer, byteArrayOffset + pos, value);pos += SIZE_OF_LONG;}public long getLong(){long value = unsafe.getLong(buffer, byteArrayOffset + pos);pos += SIZE_OF_LONG;return value;}public void putLongArray(final long[] values){putInt(values.length);long bytesToCopy = values.length << 3;unsafe.copyMemory(values, longArrayOffset,buffer, byteArrayOffset + pos,bytesToCopy);pos += bytesToCopy;}public long[] getLongArray(){int arraySize = getInt();long[] values = new long[arraySize];long bytesToCopy = values.length << 3;unsafe.copyMemory(buffer, byteArrayOffset + pos,values, longArrayOffset,bytesToCopy);pos += bytesToCopy;return values;}public void putDoubleArray(final double[] values){putInt(values.length);long bytesToCopy = values.length << 3;unsafe.copyMemory(values, doubleArrayOffset,buffer, byteArrayOffset + pos,bytesToCopy);pos += bytesToCopy;}public double[] getDoubleArray(){int arraySize = getInt();double[] values = new double[arraySize];long bytesToCopy = values.length << 3;unsafe.copyMemory(buffer, byteArrayOffset + pos,values, doubleArrayOffset,bytesToCopy);pos += bytesToCopy;return values;}
}

結果

2.8GHz Nehalem - Java 1.7.0_04
==============================
0 Serialisation  write=2,517ns read=11,570ns total=14,087ns
1 Serialisation  write=2,198ns read=11,122ns total=13,320ns
2 Serialisation  write=2,190ns read=11,011ns total=13,201ns
3 Serialisation  write=2,221ns read=10,972ns total=13,193ns
4 Serialisation  write=2,187ns read=10,817ns total=13,004ns
0 ByteBuffer     write=264ns   read=273ns    total=537ns
1 ByteBuffer     write=248ns   read=243ns    total=491ns
2 ByteBuffer     write=262ns   read=243ns    total=505ns
3 ByteBuffer     write=300ns   read=240ns    total=540ns
4 ByteBuffer     write=247ns   read=243ns    total=490ns
0 UnsafeMemory   write=99ns    read=84ns     total=183ns
1 UnsafeMemory   write=53ns    read=82ns     total=135ns
2 UnsafeMemory   write=63ns    read=66ns     total=129ns
3 UnsafeMemory   write=46ns    read=63ns     total=109ns
4 UnsafeMemory   write=48ns    read=58ns     total=106ns2.4GHz Sandy Bridge - Java 1.7.0_04
===================================
0 Serialisation  write=1,940ns read=9,006ns total=10,946ns
1 Serialisation  write=1,674ns read=8,567ns total=10,241ns
2 Serialisation  write=1,666ns read=8,680ns total=10,346ns
3 Serialisation  write=1,666ns read=8,623ns total=10,289ns
4 Serialisation  write=1,715ns read=8,586ns total=10,301ns
0 ByteBuffer     write=199ns   read=198ns   total=397ns
1 ByteBuffer     write=176ns   read=178ns   total=354ns
2 ByteBuffer     write=174ns   read=174ns   total=348ns
3 ByteBuffer     write=172ns   read=183ns   total=355ns
4 ByteBuffer     write=174ns   read=180ns   total=354ns
0 UnsafeMemory   write=38ns    read=75ns    total=113ns
1 UnsafeMemory   write=26ns    read=52ns    total=78ns
2 UnsafeMemory   write=26ns    read=51ns    total=77ns
3 UnsafeMemory   write=25ns    read=51ns    total=76ns
4 UnsafeMemory   write=27ns    read=50ns    total=77ns

分析

使用Java序列化在我的快速2.4 GHz Sandy Bridge筆記本電腦上寫和讀一個相對較小的對象可能需要10,000ns,而使用Unsafe時,即使考慮到測試代碼本身,也可以減少到不到100ns。 為了說明這一點,在使用Java序列化時,成本與網絡躍點相當! 如果您的傳輸是同一系統上的快速IPC機制,那么這將是非常昂貴的。

Java序列化如此昂貴的原因有很多。 例如,它為每個對象寫出完全限定的類和字段名稱以及版本信息。 同樣, ObjectOutputStream保留所有書面對象的集合,以便在調用close()時可以將它們合并。 對于此示例對象,Java序列化需要340字節,但是對于二進制版本,我們僅需要185字節。 Java序列化格式的詳細信息可以在這里找到。 如果我沒有使用數組存儲大多數數據,那么由于字段名的原因,使用Java序列化,序列化的對象會大很多。 以我的經驗,諸如XML和JSON之類的基于文本的協議甚至可能比Java序列化的效率更低。 還應注意Java序列化是RMI所采用的標準機制。

真正的問題是要執行的指令數量。 Unsafe方法有很大優勢,因為在Hotspot和許多其他JVM中,優化器將這些操作視為內部操作,并用匯編指令替換了調用以執行內存操作。 對于基本類型,這將導致單個x86 MOV指令,該指令通常可以在單個周期內發生。 如我在上一篇文章中所述,可以通過讓Hotspot輸出優化的代碼來看到詳細信息。

現在必須說,“ 功能強大,責任重大 ”,如果您使用Unsafe,它實際上與C語言編程相同,并且當偏移量錯誤時,也會發生內存訪問沖突。

添加一些上下文

“ Google協議緩沖區之類的怎么樣?”,聽說您大聲疾呼。 這些是非常有用的庫,通常可以比Java序列化提供更好的性能和更大的靈活性。 但是,它們并不像我在此處所示的那樣接近使用Unsafe的性能。 協議緩沖區解決了一個不同的問題,并提供了很好的自描述消息,這些消息在各種語言之間都可以正常工作。 請使用不同的協議和序列化技術進行測試以比較結果。

另外你之間的精明會問,“什么字節順序的整數(字節順序)寫的?” 使用不安全時,字節以本機順序寫入。 這對于IPC以及相同類型的系統之間非常有用。 如果系統使用不同的格式,則必須進行轉換。

我們如何處理一個類的多個版本或如何確定對象所屬的類? 我想讓本文重點關注,但讓我們說一個簡單的整數來表示實現類是標題所需的全部。 該整數可用于查找反序列化操作的適當實現。

我經常聽到反對二進制協議和文本協議的爭論,那么人類可讀和調試該怎么辦? 有一個簡單的解決方案。 開發用于讀取二進制格式的工具!

結論

總之,可以通過有效使用相同的技術,在Java中實現相同的本機C / C ++性能級別,以將對象與字節流進行串行化。 我已為其提供了基本實現的UnsafeMemory類,可以輕松擴展以封裝此行為,從而在使用這種敏銳的工具時可以保護自己免受許多潛在問題的影響。

現在是急需解決的問題。 如果Java通過本地提供我對Unsafe所做的有效工作來為Serializable提供替代的Marshallable接口,會不會更好呢???

參考: Mechanical Sympathy博客上的JCG合作伙伴 Martin Thompson提供的Java對象序列化的本機C / C ++類性能,用于Java對象序列化 。


翻譯自: https://www.javacodegeeks.com/2012/07/native-cc-like-performance-for-java.html

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

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

相關文章

WebStrom Sass 編譯配置 windows

第一步&#xff1a; 先安裝Ruby下載 一路next 安裝完成后打開開始菜單 打開后輸入 gem install sass sass -v 出現版本號說明成功 第二部配置webstorm 在webstorm中settings中搜索file watchers工具&#xff0c;在此工具中添加一個scss的watcher 確定&#xff0c;打開一個scss…

非本地跳轉之setjmp與longjmp

非本地跳轉(unlocal jump)是與本地跳轉相對應的一個概念。 本地跳轉主要指的是類似于goto語句的一系列應用&#xff0c;當設置了標志之后&#xff0c;可以跳到所在函數內部的標號上。然而&#xff0c;本地跳轉不能將控制權轉移到所在程序的任意地點&#xff0c;不能跨越函數&am…

清華計算機自主招生試題,2017年清華大學自主招生筆試題

2017年清華大學自主招生筆試題2017高考結束后&#xff0c;全國各大高校自主招生面試開始了&#xff0c;以下是百分網小編搜索整理的關于2017年清華大學自主招生筆試題&#xff0c;供各位參考&#xff0c;希望對大家有所幫助!想了解更多相關信息請持續關注我們應屆畢業生考試網!…

擴展劑:模式還是反模式?

擴展器模式在最近幾年變得很流行&#xff0c;甚至已經在OSGi標準&#xff08;例如&#xff0c;藍圖服務和Web應用程序規范&#xff09;中使用。 在處女座&#xff0c;我們從一開始就與擴展程序一起工作&#xff0c;但是盡管它們具有優勢&#xff0c;但它們仍有一些明顯的缺點。…

python html格式編碼

web應用如用到ace等網絡編輯器的時候&#xff0c;如要支持html,xml等格式的文件編輯&#xff0c;輸入ace 的文本內容必須先進行html格式編碼&#xff1a; def html_escape(content): import cgi return cgi.escape(content)轉載于:https://www.cnblogs.com/zhouxiaoming/p/703…

字符串替換

題目: 給定一個英文的字符串, 要求你將其中的元音刪除掉, 返回新的字符串. 例如:"This website is for losers LOL!" --> "Ths wbst s fr lsrs LL!" 當看到這個題目的時候, 第一個想起的就是re模塊的正則表達式. 不過由于最近使用過字符串的replace方…

小學計算機技術指導綱要,《中小學信息技術課程指導綱要(試行)》

《中小學信息技術課程指導綱要(試行)》2000年11月教育部頒發的《中小學信息技術課程指導綱要(試行)》教學目標&#xff1a;1、 增強信息意識&#xff0c;了解信息技術的發展變化及其對工作和社會的影響。2、 初步了解計算機基本工作原理&#xff0c;學會使用與學習和實際生活直…

JavaFX 2.0布局窗格– FlowPane和TilePane

FlowPanes和TilePanes是不錯的布局窗格&#xff0c;如果您想一個接一個地連續地水平或垂直地布局子級&#xff0c;則可以。 它們彼此非常相似&#xff0c;因為它們都將子級布置成列&#xff08;在水平Flow / TilePane的情況下&#xff09;并按其寬度或行&#xff08;在垂直Flow…

EasyRMS錄播管理服務器項目實戰:windows上開機自啟動NodeJS服務

本文轉自EasyDarwin開源團隊成員Penggy的博客&#xff1a;http://www.jianshu.com/p/ef840505ae06 近期在EasyDarwin開源團隊開發一款基于EasyDarwin在錄播服務器EasyRMS過程中,我采用node作為EasyRMS錄播服務器錄播管理服務器的開發平臺,基于node開發關于設備管理,錄像計劃,錄…

windows10搭建django1.10.3+Apache2.4

很多教程都是在linux上搭建&#xff0c;windows上似乎天生不太適合&#xff0c;但是我還是愿意試試這個坑。 首先 交代一下自己的環境 python3.5.2 64位 django 1.10.3 apache 2.4 64位 windows 10 重點在apache上。 python 和django 相信有興趣看這篇文章的基本上也都已經了解…

深入理解計算機系統 視頻教程,深入理解計算機系統1

第一章 計算機系統漫游代碼段的生命周期hello.c#include int main(){printf("hello world!\n");return 0;}1.1 前序源程序(源文件)實際上就是一個由0和1組成的位(又成比特bit)序列,8個位被組組成一組,稱為字節。每個字節表示程序中的某些文本字符(大部分的現代計算機…

Java與iOS對話:Java對象與Apple plist序列化

我很高興地宣布我的第一個開源項目java-plist-serializer可以幫助您將Java&#xff08;尤其是基于Spring的應用程序&#xff09;與iOS應用程序集成。 背景 我正在將Java Webapp作為后端并且客戶端是iOS設備的項目。 最近&#xff0c;我收到了創建Web服務的任務&#xff0c;該服…

web.cofing(新手必看)

花了點時間整理了一下ASP.NET Web.config配置文件的基本使用方法。很適合新手參看&#xff0c;由于Web.config在使用很靈活&#xff0c;可以自定義一些節點。所以這里只介紹一些比較常用的節點。 <?xml version"1.0"?> <!--注意: 除了手動編輯此文件以外&…

MIPS下CPU和RAM的數據流動情況詳解

這是計算機硬件間的數據路徑&#xff08;即數據流動的路徑&#xff09;&#xff0c;下面將較詳細分析此圖&#xff1a; PC&#xff08;program counter&#xff0c; 程序計數器&#xff09;是一個用于記錄當前計算機正在執行的指令的地址的寄存器&#xff08;register&#xff…

計算機亂程序怎么辦,我的電腦程序亂了怎么辦

我的電腦程序亂了&#xff0c;想用光盤恢復一下系統的修復安裝方法第一種方法&#xff1a;1、點擊“開始”菜單&#xff0c;點擊“運行”2、輸入CMD回車3、輸入命令SFC/SCANNOW4、插入系統光盤系統會自動將硬盤中的系統文件于系統盤中的文件比較并進行修復如果不行&#xff0c;…

【計算機網絡】網絡層——IP協議

目錄 一. 基本概念 二. 協議報文格式 三. 網段劃分 1. 第一次劃分 2. CIDR方案 3. 特殊的IP地址 四. IP地址不足 1. 私有IP和公網IP 2. DHCP協議 3. 路由器 4. NAT技術 內網穿透(NAT穿透) 五. 路由轉發 路由表生成算法 結束語 一. 基本概念 IP指網絡互連協議…

完整的Web應用程序Tomcat JSF Primefaces JPA Hibernate –第2部分

托管豆 這篇文章是本教程第1部分的繼續。 在“ com.mb”包中&#xff0c;您將需要創建以下類&#xff1a; package com.mb;import org.primefaces.context.RequestContext;import com.util.JSFMessageUtil;public class AbstractMB {private static final String KEEP_DIALOG…

P1014 Cantor表

洛谷 p1014 題目描述 現代數學的著名證明之一是Georg Cantor證明了有理數是可枚舉的。他是用下面這一張表來證明這一命題的&#xff1a; 1/1 1/2 1/3 1/4 1/5 … 2/1 2/2 2/3 2/4 … 3/1 3/2 3/3 … 4/1 4/2 … 5/1 … … 我們以Z字形給上表的每一項編號。第一項是1/1&#xff…

dvd管理系統

>>>>>>>>>>>>>>>>>>>> 語言&#xff1a;java 工具&#xff1a;eclipse 時間&#xff1a;2016.12.1 >>>>>>>>>>>>>>>>>>>> 一代代碼&#xff1a; 1 …

佳能2900打印機與win10不兼容_佳能RF 1.4、RF 2增倍鏡與RF 100500mm L IS USM并不完全兼容...

據佳能官方透露&#xff0c;佳能RF 1.4、RF 2增倍鏡與RF 100-500mm F4.5-7.1 L IS USM鏡頭并不完全兼容。在安裝使用兩款增倍鏡時&#xff0c;用戶需將RF 100-500mm鏡頭變焦環的變焦位置移動到超過300mm的遠攝區域。而在搭配增倍鏡后&#xff0c;鏡頭變焦范圍將限定在300-500mm…