Index: src/wp-includes/functions.php
===================================================================
--- src/wp-includes/functions.php	(revision 36196)
+++ src/wp-includes/functions.php	(working copy)
@@ -1778,6 +1778,20 @@
 }
 
 /**
+ * Get uploads directory information.
+ *
+ * Same as wp_upload_dir() but "light weight" as it doesn't attempt to create the uploads directory.
+ * Intended for use in themes, when only 'basedir' and 'baseurl' are needed, generally in all cases when not uploading files.
+ *
+ * @since 4.5.0
+ *
+ * @return array See wp_upload_dir() for description.
+ */
+function wp_get_upload_dir() {
+	return wp_upload_dir( null, false );
+}
+
+/**
  * Get an array containing the current upload directory's path and url.
  *
  * Checks the 'upload_path' option, which should be from the web root folder,
@@ -1802,14 +1816,73 @@
  * 'subdir' - sub directory if uploads use year/month folders option is on.
  * 'basedir' - path without subdir.
  * 'baseurl' - URL path without subdir.
- * 'error' - set to false.
+ * 'error' - false or error message.
  *
  * @since 2.0.0
+ * @uses _wp_upload_dir()
  *
  * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
+ * @param bool   $create_dir Optional. Whether to check and create the uploads directory. Default true (backwards compatible).
+ * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
  * @return array See above for description.
  */
-function wp_upload_dir( $time = null ) {
+function wp_upload_dir( $time = null, $create_dir = true, $refresh_cache = false ) {
+	static $cache = array();
+
+	$key = sprintf( '%d-%s', get_current_blog_id(), (string) $time );
+
+	if ( $refresh_cache || empty( $cache[ $key ] ) ) {
+		$cache[ $key ] = _wp_upload_dir( $time );
+	}
+
+	/**
+	 * Filter the uploads directory data.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $uploads Array of upload directory data with keys of 'path',
+	 *                       'url', 'subdir, 'basedir', and 'error'.
+	 */
+	$uploads = apply_filters( 'upload_dir', $cache[ $key ] );
+
+	if ( $create_dir ) {
+		$path = $uploads['path'];
+		$tested_paths = wp_cache_get( 'upload_dir_tested_paths' );
+
+		if ( ! is_array( $tested_paths ) ) {
+			$tested_paths = array();
+		}
+
+		if ( array_key_exists( $path, $tested_paths ) ) {
+			$uploads['error'] = $tested_paths[ $path ];
+		} else {
+			if ( ! wp_mkdir_p( $uploads['path'] ) ) {
+				if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
+					$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
+				} else {
+					$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
+				}
+
+				$uploads['error'] = sprintf( __( 'Unable to create directory %s. Is its parent directory writable by the server?' ), $error_path );
+			}
+
+			$tested_paths[ $path ] = $uploads['error'];
+			wp_cache_set( 'upload_dir_tested_paths', $tested_paths );
+		}
+	}
+
+	return $uploads;
+}
+
+/**
+ * A non-filtered, non-cached version of wp_upload_dir() that doesn't check the path.
+ *
+ * @access private
+ *
+ * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
+ * @return array See wp_upload_dir()
+ */
+function _wp_upload_dir( $time = null ) {
 	$siteurl = get_option( 'siteurl' );
 	$upload_path = trim( get_option( 'upload_path' ) );
 
@@ -1898,35 +1971,15 @@
 	$dir .= $subdir;
 	$url .= $subdir;
 
-	/**
-	 * Filter the uploads directory data.
-	 *
-	 * @since 2.0.0
-	 *
-	 * @param array $uploads Array of upload directory data with keys of 'path',
-	 *                       'url', 'subdir, 'basedir', and 'error'.
-	 */
-	$uploads = apply_filters( 'upload_dir',
-		array(
-			'path'    => $dir,
-			'url'     => $url,
-			'subdir'  => $subdir,
-			'basedir' => $basedir,
-			'baseurl' => $baseurl,
-			'error'   => false,
-		) );
+	$uploads = array(
+		'path'    => $dir,
+		'url'     => $url,
+		'subdir'  => $subdir,
+		'basedir' => $basedir,
+		'baseurl' => $baseurl,
+		'error'   => false,
+	);
 
-	// Make sure we have an uploads directory.
-	if ( ! wp_mkdir_p( $uploads['path'] ) ) {
-		if ( 0 === strpos( $uploads['basedir'], ABSPATH ) )
-			$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
-		else
-			$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
-
-		$message = sprintf( __( 'Unable to create directory %s. Is its parent directory writable by the server?' ), $error_path );
-		$uploads['error'] = $message;
-	}
-
 	return $uploads;
 }
 
Index: tests/phpunit/includes/functions.php
===================================================================
--- tests/phpunit/includes/functions.php	(revision 36196)
+++ tests/phpunit/includes/functions.php	(working copy)
@@ -115,3 +115,18 @@
 function _set_default_permalink_structure_for_tests() {
 	update_option( 'permalink_structure', '/%year%/%monthnum%/%day%/%postname%/' );
 }
+
+/**
+ * Remove the subdir (year/month) part from the uploads 'path' and 'url'.
+ *
+ * For use when testing uploads that are not stored in sub directory.
+ */
+function _upload_dir_no_subdir( $uploads ) {
+	$subdir = $uploads['subdir'];
+
+	$uploads['subdir'] = '';
+	$uploads['path'] = str_replace( $subdir, '', $uploads['path'] );
+	$uploads['url'] = str_replace( $subdir, '', $uploads['url'] );
+
+	return $uploads;
+}
Index: tests/phpunit/tests/media.php
===================================================================
--- tests/phpunit/tests/media.php	(revision 36196)
+++ tests/phpunit/tests/media.php	(working copy)
@@ -785,11 +785,8 @@
 	function test_wp_calculate_image_srcset_no_date_uploads() {
 		global $_wp_additional_image_sizes;
 
-		// Save the current setting for uploads folders
-		$uploads_use_yearmonth_folders = get_option( 'uploads_use_yearmonth_folders' );
-
 		// Disable date organized uploads
-		update_option( 'uploads_use_yearmonth_folders', 0 );
+		add_filter( 'upload_dir', '_upload_dir_no_subdir' );
 
 		// Make an image.
 		$filename = DIR_TESTDATA . '/images/test-image-large.png';
@@ -827,9 +824,7 @@
 
 		// Remove the attachment
 		wp_delete_attachment( $id );
-
-		// Leave the uploads option the way you found it.
-		update_option( 'uploads_use_yearmonth_folders', $uploads_use_yearmonth_folders );
+		remove_filter( 'upload_dir', '_upload_dir_no_subdir' );
 	}
 
 	/**
Index: tests/phpunit/tests/upload.php
===================================================================
--- tests/phpunit/tests/upload.php	(revision 36196)
+++ tests/phpunit/tests/upload.php	(working copy)
@@ -26,9 +26,11 @@
 	function test_upload_dir_default() {
 		// wp_upload_dir() with default parameters
 		$info = wp_upload_dir();
-		$this->assertEquals( get_option( 'siteurl' ) . '/wp-content/uploads/' . gmstrftime('%Y/%m'), $info['url'] );
-		$this->assertEquals( ABSPATH . 'wp-content/uploads/' . gmstrftime('%Y/%m'), $info['path'] );
-		$this->assertEquals( gmstrftime('/%Y/%m'), $info['subdir'] );
+		$subdir = gmstrftime('/%Y/%m');
+
+		$this->assertEquals( get_option( 'siteurl' ) . '/wp-content/uploads' . $subdir, $info['url'] );
+		$this->assertEquals( ABSPATH . 'wp-content/uploads' . $subdir, $info['path'] );
+		$this->assertEquals( $subdir, $info['subdir'] );
 		$this->assertEquals( '', $info['error'] );
 	}
 
@@ -35,12 +37,13 @@
 	function test_upload_dir_relative() {
 		// wp_upload_dir() with a relative upload path that is not 'wp-content/uploads'
 		update_option( 'upload_path', 'foo/bar' );
-		$info = wp_upload_dir();
+		$info = wp_upload_dir( null, true, true );
 		$this->delete_folders( ABSPATH . 'foo' );
+		$subdir = gmstrftime('/%Y/%m');
 
-		$this->assertEquals( get_option( 'siteurl' ) . '/foo/bar/' . gmstrftime('%Y/%m'), $info['url'] );
-		$this->assertEquals( ABSPATH . 'foo/bar/' . gmstrftime('%Y/%m'), $info['path'] );
-		$this->assertEquals( gmstrftime('/%Y/%m'), $info['subdir'] );
+		$this->assertEquals( get_option( 'siteurl' ) . '/foo/bar' . $subdir, $info['url'] );
+		$this->assertEquals( ABSPATH . 'foo/bar' . $subdir, $info['path'] );
+		$this->assertEquals( $subdir, $info['subdir'] );
 		$this->assertEquals( '', $info['error'] );
 	}
 
@@ -53,18 +56,20 @@
 		update_option( 'upload_path', $path );
 		// doesn't make sense to use an absolute file path without setting the url path
 		update_option( 'upload_url_path', '/baz' );
-		$info = wp_upload_dir();
+		$info = wp_upload_dir( null, true, true );
 		$this->delete_folders( $path );
+		$subdir = gmstrftime('/%Y/%m');
 
-		$this->assertEquals( '/baz/' . gmstrftime('%Y/%m'), $info['url'] );
-		$this->assertEquals( "$path/" . gmstrftime('%Y/%m'), $info['path'] );
-		$this->assertEquals( gmstrftime('/%Y/%m'), $info['subdir'] );
+		$this->assertEquals( '/baz' . $subdir, $info['url'] );
+		$this->assertEquals( $path . $subdir, $info['path'] );
+		$this->assertEquals( $subdir, $info['subdir'] );
 		$this->assertEquals( '', $info['error'] );
 	}
 
 	function test_upload_dir_no_yearnum() {
 		update_option( 'uploads_use_yearmonth_folders', 0 );
-		$info = wp_upload_dir();
+		$info = wp_upload_dir( null, true, true );
+
 		$this->assertEquals( get_option( 'siteurl' ) . '/wp-content/uploads', $info['url'] );
 		$this->assertEquals( ABSPATH . 'wp-content/uploads', $info['path'] );
 		$this->assertEquals( '', $info['subdir'] );
@@ -73,10 +78,12 @@
 
 	function test_upload_path_absolute() {
 		update_option( 'upload_url_path', 'http://example.org/asdf' );
-		$info = wp_upload_dir();
-		$this->assertEquals( 'http://example.org/asdf/' . gmstrftime('%Y/%m'), $info['url'] );
-		$this->assertEquals( ABSPATH . 'wp-content/uploads/' . gmstrftime('%Y/%m'), $info['path'] );
-		$this->assertEquals( gmstrftime('/%Y/%m'), $info['subdir'] );
+		$info = wp_upload_dir( null, true, true );
+		$subdir = gmstrftime('/%Y/%m');
+
+		$this->assertEquals( 'http://example.org/asdf' . $subdir, $info['url'] );
+		$this->assertEquals( ABSPATH . 'wp-content/uploads' . $subdir, $info['path'] );
+		$this->assertEquals( $subdir, $info['subdir'] );
 		$this->assertEquals( '', $info['error'] );
 	}
 
@@ -83,10 +90,12 @@
 	function test_upload_dir_empty() {
 		// upload path setting is empty - it should default to 'wp-content/uploads'
 		update_option('upload_path', '');
-		$info = wp_upload_dir();
-		$this->assertEquals( get_option( 'siteurl' ) . '/wp-content/uploads/' . gmstrftime('%Y/%m'), $info['url'] );
-		$this->assertEquals( ABSPATH . 'wp-content/uploads/' . gmstrftime('%Y/%m'), $info['path'] );
-		$this->assertEquals( gmstrftime('/%Y/%m'), $info['subdir'] );
+		$info = wp_upload_dir( null, true, true );
+		$subdir = gmstrftime('/%Y/%m');
+
+		$this->assertEquals( get_option( 'siteurl' ) . '/wp-content/uploads' . $subdir, $info['url'] );
+		$this->assertEquals( ABSPATH . 'wp-content/uploads' . $subdir, $info['path'] );
+		$this->assertEquals( $subdir, $info['subdir'] );
 		$this->assertEquals( '', $info['error'] );
 	}
 
