This workbook can help rank GPUs according a mixture of features (with the weights determined by the user) and graph it against price.
However, Amazon has now put a price-grabber-blocker in place, so it probably makes sense to switch to NewEgg - or a price grabbing service (that would keep their IDs consistent over time by default).
If this fails when downloading the price data, make sure that you have installed Python's Beautiful Soup package (for HTML parsing) : pip install beautifulsoup4
.
Firstly, pull in the parameters from Wikipedia (NVidia cards, AMD cards) for the cards under consideration (more can easily be added, though, to keep the list reasonable, let's ignore cards with <1000 single precision GFLOPs), each with one example of the product on Amazon or NewEgg (more Amazon/NewEgg examples can be added below) :
In [1]:
raw="""
name | sh:tx:rop | mem | bw | bus | ocl |single|double|watts| passmark
GeForce GT 740 | 384:32:16 | 4096 | 28 | 128 | 1.2 | 763 | 0 | 65 | 1579
GeForce GTX 750 | 512:32:16 | 2048 | 80 | 128 | 1.2 | 1044 | 32 | 55 | 3271
GeForce GTX 750 Ti | 640:40:16 | 4096 | 80 | 128 | 1.2 | 1306 | 40 | 60 | 3695
GeForce GTX 760 | 1152:96:32 | 4096 | 192 | 256 | 1.2 | 2257 | 94 | 170 | 4952
GeForce GTX 760 Ti | 1152:96:32 | 4096 | 192 | 256 | 1.2 | 2257 | 94 | 170 | 5059
GeForce GTX 960 | 1024:64:32 | 4096 | 112 | 128 | 1.2 | 2308 | 72 | 120 | 5828
GeForce GTX 970 | 1664:104:56 | 3584 | 196 | 224 | 1.2 | 3494 | 109 | 145 | 8573
GeForce GTX 980 | 2048:128:64 | 4096 | 224 | 256 | 1.2 | 4612 | 144 | 165 | 9592
GeForce GTX 980 Ti | 2816:176:96 | 6144 | 336 | 384 | 1.2 | 5632 | 176 | 250 |11350
GeForce GTX Titan X | 3072:192:96 | 12288 | 336 | 384 | 1.2 | 6144 | 192 | 250 |10669
GeForce GTX 1060 3GB | 1152:72:48 | 3072 | 192 | 192 | 1.2 | 3470 | 108 | 120 | 8567
GeForce GTX 1060 6GB | 1280:80:48 | 6144 | 192 | 192 | 1.2 | 3855 | 120 | 120 | 8662
GeForce GTX 1070 | 1920:120:64 | 8192 | 256 | 256 | 1.2 | 5783 | 181 | 150 |10906
GeForce GTX 1080 | 2560:160:64 | 8192 | 320 | 256 | 1.2 | 8228 | 257 | 180 |11982
GeForce GTX 1080 Ti | 3584:224:88 | 11264 | 484 | 352 | 1.2 |10609 | 332 | 250 |13247
NVIDIA TITAN Xp | 3584:224:96 | 12288 | 480 | 384 | 1.2 |10157 | 317 | 250 |14894
"""
sadly_waiting_on_decent_drivers = """
Radeon HD 5570 | 400:20:8 | 1024 | 29 | 128 | 1.2 | 520 | 0 | 39 | 712
Radeon R9 280 | 1792:112:32 | 3072 | 240 | 384 | 1.2 | 2964 | 741 | 250 | 5283
Radeon R9 290 / 390 | 2560:160:64 | 4096 | 320 | 512 | 2 | 4848 | 606 | 275 | 7026
Radeon R9 290X / 390X | 2816:176:64 | 4096 | 320 | 512 | 2 | 5632 | 704 | 290 | 7306
Radeon R9 390X | 2816:176:64 | 8192 | 384 | 512 | 2.1 | 5914 | 739 | 275 | 8431
Radeon RX 480
"""
import re
arr = [ re.split(r'\s*[|:]\s*',l) for l in raw.split('\n') if len(l)>0]
headings = arr[0]
#print(headings)
cards={ a[0]:{ h:(e if h in ':name:' else float(e)) for h,e in zip(headings,a) } for a in arr[1:] }
# Create a place for the pricing to go (with the PassMark entry there for starters)
for c in cards.keys(): cards[c]['pricing']={c:dict(px=None,brand='PassMark',comment='')}
Now the GPU card data is in a nice array of dictionary entries, with numeric entries for all but 'name' which matches the corresponding entry on the GPU Passmarks benchmarks list.
Here, one can put additional Amazon product codes that refer to the same card from a Compute perspective (different manufacturer and/or different ports may make the cards different from a gaming user's perspective, of course).
TODO : Add in more prices, to get a broader sample
In [2]:
#pricing={ a['sku']:{k:v for k,v in a.items() if k in 'name:brand:comment:sku:pm'} for a in cards}
#for c in cards:print("%s|%s" % (c['name'], c['amz']))
In [3]:
raw="""
name |sku:brand:comment
GeForce GT 740 | B00KJGYOBQ
GeForce GTX 750 | B00J3ZNB04
GeForce GTX 750 Ti | B00T4RJ8FI
GeForce GTX 760 | B00E9O28DU
GeForce GTX 960 | B00UOYQ5LA
GeForce GTX 970 | B00NVODXR4
GeForce GTX 970 | B00NH5ZNWA:PNY
GeForce GTX 970 | B00OQUMGM0:GigabyteMiniITX
GeForce GTX 980 | B00NT9UT3M
GeForce GTX 980 Ti | B00YNEIAWY
GeForce GTX 980 Ti | B00YDAYOK0:EVGA
GeForce GTX Titan X | B00UXTN5P0
GeForce GTX 1060 3GB | B01KUADE3O
GeForce GTX 1060 6GB | B01IPVSGEC
GeForce GTX 1070 | B01GLRX81I
GeForce GTX 1080 | B01IR6LMLO
GeForce GTX 1080 Ti | B06XH2P8DD
NVIDIA TITAN Xp | B01JLKP3IS
"""
sadly_waiting_for_decent_drivers = """
HD 5570 1Gb | B004JU260O
R9 280 | B00IZXOW80
R9 290 | B00V4JVY1A
R9 290X | B00FLMKQY2
R9 380 2Gb | B00ZGL8EBK
R9 380 4Gb | B00ZGF3TUC
R9 390 8Gb | B00ZGL8CYY
R9 390 8Gb | B00ZGF0UAE:MSI
R9 390 8Gb | B00ZGF3UAQ:Gigabyte
R9 390 8Gb | B00ZGL8CYY:Sapphire
R9 390 8Gb | B00ZQ3QVU4:Asus
R9 390 8Gb | B00ZQ9JKSS:Visiontech
R9 390X | B00ZGL8CFI
R9 390X | B00ZGF158A:MSI
R9 390X | B00ZGF3TNO:Gigabyte
R9 390X | B00ZGL8CFI:Sapphire
"""
arr = [ re.split(r'\s*[|:]\s*',l) for l in raw.split('\n') if len(l)>0]
headings = arr[0]
for a in arr[1:]:
data={ h:e for h,e in zip(headings,a) }
if a[0] in cards:
cards[a[0]]['pricing'][data['sku']]=data
else:
print("Card named '%s' not found in core listing" % (a[0],))
#pricing.update({ a['sku']:a for a in equivs if a['sku'] })
#pricing
In [4]:
cache={'B00UXTN5P0': 737.00, 'B00IDG3NDY': 114.12, 'B00DT5R3EO': 199.99, 'B004JU260O': 180.99, 'B00ZGL8EBK': 216.53, 'B01IPVSGEC': 230.39, 'B00ZGF158A': 429.99, 'B00NVODXR4': 337.99, 'B00UOYQ5LA': 239.99, 'B00ZQ3QVU4': 349.99, 'B00KJGYOBQ': 99.99, 'B01JLKP3IS': 1499.99, 'B00YNEIAWY': 698.85, 'B00OQUMGM0': 299.99, 'B06XH2P8DD': 699.99, 'B00IDG3IDO': 139.99, 'B00ZGF3UAQ': 329.99, 'B00J3ZNB04': 149.37, 'B00FLMKQY2': 339.99, 'B01KUADE3O': 189.99, 'B00SC6HAS4': 199.99, 'B00ZGF3TUC': 229.99, 'B00ZGF0UAE': 369.99, 'B00ZGL8CFI': 458.63, 'B01IR6LMLO': 469.99, 'B00T4RJ8FI': 349.99, 'B00ZQ9JKSS': 368.63, 'B00ZGL8CYY': 359.42, 'B00V4JVY1A': 333.26, 'B00IZXOW80': 249.99, 'B00YDAYOK0': 679.99, 'B00E9O28DU': 274.99, 'B00NT9UT3M': 507.82, 'B00ZGF3TNO': 499.99}
cache.update(
{'B00ZQ3QVU4': 349.99, 'B01JLKP3IS': 1499.99, 'B01GX5YWAO': 399.99, 'B00DT5R3EO': 199.99, 'B00UOYQ5LA': 239.99, 'B01KUADE3O': 189.99, 'B00V4JVY1A': 333.26, 'B00FLMKQY2': 366.08, 'B00J3ZNB04': 149.37, 'B00ZGF3UAQ': 361.79, 'B00IDG3IDO': 139.99, 'B06XH2P8DD': 699.99, 'B00KJGYOBQ': 99.99, 'B004JU260O': 159.99, 'B01IPVSGEC': 230.39, 'B00IDG3NDY': 114.12, 'B00YNEIAWY': 698.85, 'B00SC6HAS4': 199.99, 'B01IR6LMLO': 469.99, 'B00T4RJ8FI': 349.99, 'B00ZGF0UAE': 399.99, 'B00OQUMGM0': 329.0, 'B00YDAYOK0': 679.99, 'B00ZGL8EBK': 216.53, 'B00ZGL8CYY': 359.42, 'B00NT9UT3M': 507.82, 'B00ZGF158A': 429.99, 'B00IZXOW80': 249.99, 'B00ZQ9JKSS': 368.63, 'B01GLRX81I': 379.89, 'B00NVODXR4': 337.99, 'B00UXTN5P0': 737.0, 'B00ZGF3TUC': 229.99, 'B00NH5ZNWA': 329.99, 'B00ZGL8CFI': 458.63, 'B00E9O28DU': 274.99, 'B00ZGF3TNO': 499.99}
)
#for k,v in cache.items():
# #if k in pricing and pricing[k].get('px',None) is None:
# if k in pricing and (pricing[k].get('px',None) is None or pricing[k]['px']<v):
# pricing[k]['px'] = v
for name,card_data in cards.items():
for sku,sku_data in card_data['pricing'].items():
if sku in cache:
v = cache[sku]
if sku_data.get('px',None) is None or sku_data['px']<v:
sku_data['px'] = v
#pricing
In [ ]:
import requests
from bs4 import BeautifulSoup
import re
In [ ]:
BASE_URL = "http://www.amazon.com/exec/obidos/ASIN/"
for name,card_data in cards.items():
for sku,sku_data in card_data['pricing'].items():
if not sku.startswith('B0'): continue # Skip non-Amazon SKUs
if sku_data.get('px', None) is None:
#if True:
print("Fetching price '%s' for %s from Amazon.com" % (sku, name))
r = requests.get(BASE_URL + sku)
print(" Got %d bytes" % (len(r.text),))
soup = BeautifulSoup(r.text, 'html.parser')
price = None
try:
ele = soup.find(id="priceblock_ourprice")
price = float(ele.text.replace('$','').replace(',',''))
except AttributeError:
print(" Didn't find the 'price' element for %s (%s)" % (name, sku))
sku_data['px']=price
print("Finished downloading Amazon prices : Run the 'cache' script below to save the data")
In [ ]:
#r.text
In [ ]:
BASE_URL = "http://www.videocardbenchmark.net/gpu_list.php"
headers = { 'User-Agent': 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0', }
r = requests.get(BASE_URL, headers=headers )
print(" Got %d bytes" % (len(r.text),))
#r.text
In [ ]:
soup = BeautifulSoup(r.text, 'html.parser')
try:
trs = soup.find(id='cputable').find_all('tr', id=True)
print("Found %d cards" % (len(trs),))
for tr in trs:
tds = tr.find_all('td')
card = tds[0].a.text
if card in cards:
px_str = tds[-1].text
try:
px = float( re.sub(r'[\$\*\,]', '', px_str))
cards[card]['pricing'][card]['px'] = px
print("%s\t%10s\t%.2f" % ((card+' '*20)[:20], px_str, px))
except ValueError:
print(" Couldn't parse the 'price' element for %s (%s)" % (card, px_str))
except AttributeError:
print(" Didn't find the 'pricing table' element")
In [ ]:
In [ ]:
# TODO print({ k:v['px'] for k,v in pricing.items() if v.get('px',None) is not None})
In [5]:
for name, data in cards.items():
pxs = [ sku_data['px'] for sku,sku_data in data['pricing'].items() if sku_data.get('px', None) is not None ]
if len(pxs)>0:
data['px_min']=min(pxs)
data['px_max']=max(pxs)
In [6]:
for name, data in sorted(cards.items()):
for sku, sku_data in data['pricing'].items():
if sku_data.get('px', None) is not None:
print("%s| $%7.2f" % ((name+' '*30)[:24], sku_data['px']))
The concept here is that one can focus on a 'basecard' (for instance, one you already have, or one you've looked at closely), and then assign multiplicative weights to each of a GPU card's qualities, and come up with a 'relative performance' according to that weighting scheme.
In [14]:
# FLOPs are twice as important as memory, all else ignored
multipliers = dict(single=2., mem=1.)
# ignore cards with OpenCL>3 (none), or prices above 1500
card_names_filtered = [name for name,data in cards.items() if data['ocl']<3. and data.get('px_min',1000000)<1000. ]
In [15]:
basecard = 'GeForce GTX 980' # Name should match a card with full data above
basedata = [ data for name, data in cards.items() if name==basecard ][0]
def evaluate_card(base, data, mult):
comp=0.
for (k,v) in mult.items():
if data.get(k,None) is not None and base.get(k,None) is not None:
comp += v*data[k]/base[k]
return comp
x=[ cards[name].get('px_min',None) for name in card_names_filtered ]
y=[ evaluate_card(basedata, cards[name], multipliers) for name in card_names_filtered ]
l=[ name for name in card_names_filtered ]
for name,score,px in sorted(zip(l,y,x), key=lambda p: -p[1]):
print("%s| $%7.2f | %5.2f" % ((name+' '*30)[:24], px, score))
In [16]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(15, 8))
plt.plot(x,y, 'ro')
plt.xlabel('Price', fontsize=16)
plt.ylabel('Score', fontsize=16)
#print( dir( plt.axes().get_xlim ))
for i, xy in enumerate(zip(x, y)):
plt.annotate('%s' % (l[i]), xy=xy, xytext=(5,.05), textcoords='offset points')
start, stop = plt.axes().get_xlim()
#plt.axes().set_xticks(np.arange(start, stop + 100., 100.)) # Force $100 units on x-axis
plt.axes().set_xticks(np.arange(0, 1000., 100.)) # Force $100 units on x-axis
plt.axes().set_yticks(np.arange(0, 8., 0.5)) # Force $100 units on x-axis
plt.grid(b=True, which='major', color='b', axis='x', linestyle='-')
plt.grid(b=True, which='major', color='b', axis='y', linestyle='--')
plt.show()
In [ ]: