We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Very slow when writing to GPKG
Here's my test suite for a proof: https://github.com/culebron/geodata

Run `python3.6 few.py` and `python3.6 multiple.py` to compare.

`few.py` opens a file with a lot of data, but only 2.7K records as GeoDataFrame. It writes them into GeoJSON and GPKG. In this case, GPKG driver outperforms GeoJSON.

`multiple.py` creates a 100K records dataframe and then saves it to GeoJSON and GPKG. Here, GPKG is incredibly slow.

My results:

	$ python3.6 few.py 
	writing 2.7K records to geojson 36.283805477003625
	writing 2.7K records to gpkg    20.792497718997765
	$ python3.6 multiple.py 
	100%|████████████████████████████████████████████████████████| 100000/100000 [00:03<00:00, 29996.25it/s]
	writing 100K records to geojson 61.62079200500011
	writing 100K records to gpkg    260.4413645050008

And notice that in case of `multiple.py`, the resulting GeoPackage file is only 9 megs. Which is times smaller than the file produced by `few.py`.

As I understand, the problem is that Fiona opens a session in Sqlite and creates a lock file, and it takes some time. And inspecting the code, I see GeoPandas naively writes everything 1 record at a time, which means Sqlite honestly locks it, then writes, then unlocks:

https://github.com/geopandas/geopandas/blob/master/geopandas/io/file.py#L107

    with fiona.drivers():
        with fiona.open(filename, 'w', driver=driver, crs=df.crs,
                        schema=schema, **kwargs) as colxn:
            for feature in df.iterfeatures():
                colxn.write(feature)


This should be optimized. Are there branches/pull requests for this?


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `geopandas/io/file.py`
Content:
```
1 import os
2 
3 import fiona
4 import numpy as np
5 import six
6 
7 from geopandas import GeoDataFrame
8 
9 # Adapted from pandas.io.common
10 if six.PY3:
11     from urllib.request import urlopen as _urlopen
12     from urllib.parse import urlparse as parse_url
13     from urllib.parse import uses_relative, uses_netloc, uses_params
14 else:
15     from urllib2 import urlopen as _urlopen
16     from urlparse import urlparse as parse_url
17     from urlparse import uses_relative, uses_netloc, uses_params
18 
19 _VALID_URLS = set(uses_relative + uses_netloc + uses_params)
20 _VALID_URLS.discard('')
21 
22 
23 def _is_url(url):
24     """Check to see if *url* has a valid protocol."""
25     try:
26         return parse_url(url).scheme in _VALID_URLS
27     except:
28         return False
29 
30 
31 def read_file(filename, **kwargs):
32     """
33     Returns a GeoDataFrame from a file or URL.
34 
35     Parameters
36     ----------
37     filename: str
38         Either the absolute or relative path to the file or URL to
39         be opened.
40     **kwargs:
41         Keyword args to be passed to the `open` or `BytesCollection` method
42         in the fiona library when opening the file. For more information on
43         possible keywords, type:
44         ``import fiona; help(fiona.open)``
45 
46     Examples
47     --------
48     >>> df = geopandas.read_file("nybb.shp")
49 
50     Returns
51     -------
52     geodataframe : GeoDataFrame
53     """
54     bbox = kwargs.pop('bbox', None)
55     if _is_url(filename):
56         req = _urlopen(filename)
57         path_or_bytes = req.read()
58         reader = fiona.BytesCollection
59     else:
60         path_or_bytes = filename
61         reader = fiona.open
62     with reader(path_or_bytes, **kwargs) as f:
63         crs = f.crs
64         if bbox is not None:
65             assert len(bbox) == 4
66             f_filt = f.filter(bbox=bbox)
67         else:
68             f_filt = f
69         gdf = GeoDataFrame.from_features(f_filt, crs=crs)
70         # re-order with column order from metadata, with geometry last
71         columns = list(f.meta["schema"]["properties"]) + ["geometry"]
72         gdf = gdf[columns]
73 
74     return gdf
75 
76 
77 def to_file(df, filename, driver="ESRI Shapefile", schema=None,
78             **kwargs):
79     """
80     Write this GeoDataFrame to an OGR data source
81 
82     A dictionary of supported OGR providers is available via:
83     >>> import fiona
84     >>> fiona.supported_drivers
85 
86     Parameters
87     ----------
88     df : GeoDataFrame to be written
89     filename : string
90         File path or file handle to write to.
91     driver : string, default 'ESRI Shapefile'
92         The OGR format driver used to write the vector file.
93     schema : dict, default None
94         If specified, the schema dictionary is passed to Fiona to
95         better control how the file is written. If None, GeoPandas
96         will determine the schema based on each column's dtype
97 
98     The *kwargs* are passed to fiona.open and can be used to write
99     to multi-layer data, store data within archives (zip files), etc.
100     """
101     if schema is None:
102         schema = infer_schema(df)
103     filename = os.path.abspath(os.path.expanduser(filename))
104     with fiona.drivers():
105         with fiona.open(filename, 'w', driver=driver, crs=df.crs,
106                         schema=schema, **kwargs) as colxn:
107             for feature in df.iterfeatures():
108                 colxn.write(feature)
109 
110 
111 def infer_schema(df):
112     try:
113         from collections import OrderedDict
114     except ImportError:
115         from ordereddict import OrderedDict
116 
117     def convert_type(in_type):
118         if in_type == object:
119             return 'str'
120         out_type = type(np.asscalar(np.zeros(1, in_type))).__name__
121         if out_type == 'long':
122             out_type = 'int'
123         return out_type
124 
125     properties = OrderedDict([
126         (col, convert_type(_type)) for col, _type in
127         zip(df.columns, df.dtypes) if col != df._geometry_column_name
128     ])
129 
130     geom_type = _common_geom_type(df)
131     if not geom_type:
132         raise ValueError("Geometry column cannot contain mutiple "
133                          "geometry types when writing to file.")
134 
135     schema = {'geometry': geom_type, 'properties': properties}
136 
137     return schema
138 
139 
140 def _common_geom_type(df):
141     # Need to check geom_types before we write to file...
142     # Some (most?) providers expect a single geometry type:
143     # Point, LineString, or Polygon
144     geom_types = df.geometry.geom_type.unique()
145 
146     from os.path import commonprefix   # To find longest common prefix
147     geom_type = commonprefix([g[::-1] for g in geom_types if g])[::-1]  # Reverse
148     if not geom_type:
149         geom_type = None
150 
151     return geom_type
152
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/geopandas/io/file.py b/geopandas/io/file.py
--- a/geopandas/io/file.py
+++ b/geopandas/io/file.py
@@ -104,8 +104,7 @@
     with fiona.drivers():
         with fiona.open(filename, 'w', driver=driver, crs=df.crs,
                         schema=schema, **kwargs) as colxn:
-            for feature in df.iterfeatures():
-                colxn.write(feature)
+            colxn.writerecords(df.iterfeatures())
 
 
 def infer_schema(df):

{"golden_diff": "diff --git a/geopandas/io/file.py b/geopandas/io/file.py\n--- a/geopandas/io/file.py\n+++ b/geopandas/io/file.py\n@@ -104,8 +104,7 @@\n     with fiona.drivers():\n         with fiona.open(filename, 'w', driver=driver, crs=df.crs,\n                         schema=schema, **kwargs) as colxn:\n-            for feature in df.iterfeatures():\n-                colxn.write(feature)\n+            colxn.writerecords(df.iterfeatures())\n \n \n def infer_schema(df):\n", "issue": "Very slow when writing to GPKG\nHere's my test suite for a proof: https://github.com/culebron/geodata\r\n\r\nRun `python3.6 few.py` and `python3.6 multiple.py` to compare.\r\n\r\n`few.py` opens a file with a lot of data, but only 2.7K records as GeoDataFrame. It writes them into GeoJSON and GPKG. In this case, GPKG driver outperforms GeoJSON.\r\n\r\n`multiple.py` creates a 100K records dataframe and then saves it to GeoJSON and GPKG. Here, GPKG is incredibly slow.\r\n\r\nMy results:\r\n\r\n\t$ python3.6 few.py \r\n\twriting 2.7K records to geojson 36.283805477003625\r\n\twriting 2.7K records to gpkg    20.792497718997765\r\n\t$ python3.6 multiple.py \r\n\t100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 100000/100000 [00:03<00:00, 29996.25it/s]\r\n\twriting 100K records to geojson 61.62079200500011\r\n\twriting 100K records to gpkg    260.4413645050008\r\n\r\nAnd notice that in case of `multiple.py`, the resulting GeoPackage file is only 9 megs. Which is times smaller than the file produced by `few.py`.\r\n\r\nAs I understand, the problem is that Fiona opens a session in Sqlite and creates a lock file, and it takes some time. And inspecting the code, I see GeoPandas naively writes everything 1 record at a time, which means Sqlite honestly locks it, then writes, then unlocks:\r\n\r\nhttps://github.com/geopandas/geopandas/blob/master/geopandas/io/file.py#L107\r\n\r\n    with fiona.drivers():\r\n        with fiona.open(filename, 'w', driver=driver, crs=df.crs,\r\n                        schema=schema, **kwargs) as colxn:\r\n            for feature in df.iterfeatures():\r\n                colxn.write(feature)\r\n\r\n\r\nThis should be optimized. Are there branches/pull requests for this?\r\n\n", "before_files": [{"content": "import os\n\nimport fiona\nimport numpy as np\nimport six\n\nfrom geopandas import GeoDataFrame\n\n# Adapted from pandas.io.common\nif six.PY3:\n    from urllib.request import urlopen as _urlopen\n    from urllib.parse import urlparse as parse_url\n    from urllib.parse import uses_relative, uses_netloc, uses_params\nelse:\n    from urllib2 import urlopen as _urlopen\n    from urlparse import urlparse as parse_url\n    from urlparse import uses_relative, uses_netloc, uses_params\n\n_VALID_URLS = set(uses_relative + uses_netloc + uses_params)\n_VALID_URLS.discard('')\n\n\ndef _is_url(url):\n    \"\"\"Check to see if *url* has a valid protocol.\"\"\"\n    try:\n        return parse_url(url).scheme in _VALID_URLS\n    except:\n        return False\n\n\ndef read_file(filename, **kwargs):\n    \"\"\"\n    Returns a GeoDataFrame from a file or URL.\n\n    Parameters\n    ----------\n    filename: str\n        Either the absolute or relative path to the file or URL to\n        be opened.\n    **kwargs:\n        Keyword args to be passed to the `open` or `BytesCollection` method\n        in the fiona library when opening the file. For more information on\n        possible keywords, type:\n        ``import fiona; help(fiona.open)``\n\n    Examples\n    --------\n    >>> df = geopandas.read_file(\"nybb.shp\")\n\n    Returns\n    -------\n    geodataframe : GeoDataFrame\n    \"\"\"\n    bbox = kwargs.pop('bbox', None)\n    if _is_url(filename):\n        req = _urlopen(filename)\n        path_or_bytes = req.read()\n        reader = fiona.BytesCollection\n    else:\n        path_or_bytes = filename\n        reader = fiona.open\n    with reader(path_or_bytes, **kwargs) as f:\n        crs = f.crs\n        if bbox is not None:\n            assert len(bbox) == 4\n            f_filt = f.filter(bbox=bbox)\n        else:\n            f_filt = f\n        gdf = GeoDataFrame.from_features(f_filt, crs=crs)\n        # re-order with column order from metadata, with geometry last\n        columns = list(f.meta[\"schema\"][\"properties\"]) + [\"geometry\"]\n        gdf = gdf[columns]\n\n    return gdf\n\n\ndef to_file(df, filename, driver=\"ESRI Shapefile\", schema=None,\n            **kwargs):\n    \"\"\"\n    Write this GeoDataFrame to an OGR data source\n\n    A dictionary of supported OGR providers is available via:\n    >>> import fiona\n    >>> fiona.supported_drivers\n\n    Parameters\n    ----------\n    df : GeoDataFrame to be written\n    filename : string\n        File path or file handle to write to.\n    driver : string, default 'ESRI Shapefile'\n        The OGR format driver used to write the vector file.\n    schema : dict, default None\n        If specified, the schema dictionary is passed to Fiona to\n        better control how the file is written. If None, GeoPandas\n        will determine the schema based on each column's dtype\n\n    The *kwargs* are passed to fiona.open and can be used to write\n    to multi-layer data, store data within archives (zip files), etc.\n    \"\"\"\n    if schema is None:\n        schema = infer_schema(df)\n    filename = os.path.abspath(os.path.expanduser(filename))\n    with fiona.drivers():\n        with fiona.open(filename, 'w', driver=driver, crs=df.crs,\n                        schema=schema, **kwargs) as colxn:\n            for feature in df.iterfeatures():\n                colxn.write(feature)\n\n\ndef infer_schema(df):\n    try:\n        from collections import OrderedDict\n    except ImportError:\n        from ordereddict import OrderedDict\n\n    def convert_type(in_type):\n        if in_type == object:\n            return 'str'\n        out_type = type(np.asscalar(np.zeros(1, in_type))).__name__\n        if out_type == 'long':\n            out_type = 'int'\n        return out_type\n\n    properties = OrderedDict([\n        (col, convert_type(_type)) for col, _type in\n        zip(df.columns, df.dtypes) if col != df._geometry_column_name\n    ])\n\n    geom_type = _common_geom_type(df)\n    if not geom_type:\n        raise ValueError(\"Geometry column cannot contain mutiple \"\n                         \"geometry types when writing to file.\")\n\n    schema = {'geometry': geom_type, 'properties': properties}\n\n    return schema\n\n\ndef _common_geom_type(df):\n    # Need to check geom_types before we write to file...\n    # Some (most?) providers expect a single geometry type:\n    # Point, LineString, or Polygon\n    geom_types = df.geometry.geom_type.unique()\n\n    from os.path import commonprefix   # To find longest common prefix\n    geom_type = commonprefix([g[::-1] for g in geom_types if g])[::-1]  # Reverse\n    if not geom_type:\n        geom_type = None\n\n    return geom_type\n", "path": "geopandas/io/file.py"}], "after_files": [{"content": "import os\n\nimport fiona\nimport numpy as np\nimport six\n\nfrom geopandas import GeoDataFrame\n\n# Adapted from pandas.io.common\nif six.PY3:\n    from urllib.request import urlopen as _urlopen\n    from urllib.parse import urlparse as parse_url\n    from urllib.parse import uses_relative, uses_netloc, uses_params\nelse:\n    from urllib2 import urlopen as _urlopen\n    from urlparse import urlparse as parse_url\n    from urlparse import uses_relative, uses_netloc, uses_params\n\n_VALID_URLS = set(uses_relative + uses_netloc + uses_params)\n_VALID_URLS.discard('')\n\n\ndef _is_url(url):\n    \"\"\"Check to see if *url* has a valid protocol.\"\"\"\n    try:\n        return parse_url(url).scheme in _VALID_URLS\n    except:\n        return False\n\n\ndef read_file(filename, **kwargs):\n    \"\"\"\n    Returns a GeoDataFrame from a file or URL.\n\n    Parameters\n    ----------\n    filename: str\n        Either the absolute or relative path to the file or URL to\n        be opened.\n    **kwargs:\n        Keyword args to be passed to the `open` or `BytesCollection` method\n        in the fiona library when opening the file. For more information on\n        possible keywords, type:\n        ``import fiona; help(fiona.open)``\n\n    Examples\n    --------\n    >>> df = geopandas.read_file(\"nybb.shp\")\n\n    Returns\n    -------\n    geodataframe : GeoDataFrame\n    \"\"\"\n    bbox = kwargs.pop('bbox', None)\n    if _is_url(filename):\n        req = _urlopen(filename)\n        path_or_bytes = req.read()\n        reader = fiona.BytesCollection\n    else:\n        path_or_bytes = filename\n        reader = fiona.open\n    with reader(path_or_bytes, **kwargs) as f:\n        crs = f.crs\n        if bbox is not None:\n            assert len(bbox) == 4\n            f_filt = f.filter(bbox=bbox)\n        else:\n            f_filt = f\n        gdf = GeoDataFrame.from_features(f_filt, crs=crs)\n        # re-order with column order from metadata, with geometry last\n        columns = list(f.meta[\"schema\"][\"properties\"]) + [\"geometry\"]\n        gdf = gdf[columns]\n\n    return gdf\n\n\ndef to_file(df, filename, driver=\"ESRI Shapefile\", schema=None,\n            **kwargs):\n    \"\"\"\n    Write this GeoDataFrame to an OGR data source\n\n    A dictionary of supported OGR providers is available via:\n    >>> import fiona\n    >>> fiona.supported_drivers\n\n    Parameters\n    ----------\n    df : GeoDataFrame to be written\n    filename : string\n        File path or file handle to write to.\n    driver : string, default 'ESRI Shapefile'\n        The OGR format driver used to write the vector file.\n    schema : dict, default None\n        If specified, the schema dictionary is passed to Fiona to\n        better control how the file is written. If None, GeoPandas\n        will determine the schema based on each column's dtype\n\n    The *kwargs* are passed to fiona.open and can be used to write\n    to multi-layer data, store data within archives (zip files), etc.\n    \"\"\"\n    if schema is None:\n        schema = infer_schema(df)\n    filename = os.path.abspath(os.path.expanduser(filename))\n    with fiona.drivers():\n        with fiona.open(filename, 'w', driver=driver, crs=df.crs,\n                        schema=schema, **kwargs) as colxn:\n            colxn.writerecords(df.iterfeatures())\n\n\ndef infer_schema(df):\n    try:\n        from collections import OrderedDict\n    except ImportError:\n        from ordereddict import OrderedDict\n\n    def convert_type(in_type):\n        if in_type == object:\n            return 'str'\n        out_type = type(np.asscalar(np.zeros(1, in_type))).__name__\n        if out_type == 'long':\n            out_type = 'int'\n        return out_type\n\n    properties = OrderedDict([\n        (col, convert_type(_type)) for col, _type in\n        zip(df.columns, df.dtypes) if col != df._geometry_column_name\n    ])\n\n    geom_type = _common_geom_type(df)\n    if not geom_type:\n        raise ValueError(\"Geometry column cannot contain mutiple \"\n                         \"geometry types when writing to file.\")\n\n    schema = {'geometry': geom_type, 'properties': properties}\n\n    return schema\n\n\ndef _common_geom_type(df):\n    # Need to check geom_types before we write to file...\n    # Some (most?) providers expect a single geometry type:\n    # Point, LineString, or Polygon\n    geom_types = df.geometry.geom_type.unique()\n\n    from os.path import commonprefix   # To find longest common prefix\n    geom_type = commonprefix([g[::-1] for g in geom_types if g])[::-1]  # Reverse\n    if not geom_type:\n        geom_type = None\n\n    return geom_type\n", "path": "geopandas/io/file.py"}]}